mirror of
https://github.com/4sval/FModel.git
synced 2026-09-25 02:29:21 -05:00
crash proof snooper
TODO: find and fix the various crashes that can happen (there's a lot)
This commit is contained in:
@@ -7,6 +7,7 @@ using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using Editor;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
@@ -116,15 +117,14 @@ public partial class App
|
||||
#else
|
||||
var filePath = Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.log");
|
||||
#endif
|
||||
const string template1 = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Enriched}: {Message:lj}{NewLine}{Exception}";
|
||||
const string template1 = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}";
|
||||
const string template2 = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{ClassName}] {ObjectPath}: {Message:lj}{NewLine}{Exception}";
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
#if DEBUG
|
||||
.Enrich.With<SourceEnricher>()
|
||||
.MinimumLevel.Verbose()
|
||||
#else
|
||||
.Enrich.With<CallerEnricher>()
|
||||
#endif
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.WithProperty("SourceContext", "FModel")
|
||||
.WriteTo.Logger(lc => lc
|
||||
.Filter.ByExcluding(IsConversionLibrary)
|
||||
.WriteTo.Console(outputTemplate: template1, theme: AnsiConsoleTheme.Literate)
|
||||
@@ -133,7 +133,8 @@ public partial class App
|
||||
.Filter.ByIncludingOnly(IsConversionLibrary)
|
||||
.WriteTo.Console(outputTemplate: template2, theme: AnsiConsoleTheme.Literate)
|
||||
.WriteTo.File(outputTemplate: template2, path: filePath, shared: true))
|
||||
// .MinimumLevel.Override("CUE4Parse_Conversion", LogEventLevel.Verbose).WriteTo.Sink(ImGuiSink.Instance)
|
||||
.MinimumLevel.Override("CUE4Parse_Conversion", LogEventLevel.Verbose)
|
||||
.WriteTo.Sink(ImGuiSink.Instance)
|
||||
.CreateLogger();
|
||||
|
||||
CacheManager.MigrateLegacyFiles();
|
||||
|
||||
107
FModel/Framework/SnooperHost.cs
Normal file
107
FModel/Framework/SnooperHost.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Editor;
|
||||
using FModel.Views.Resources.Controls;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using Serilog;
|
||||
using Serilog.Context;
|
||||
|
||||
namespace FModel.Framework;
|
||||
|
||||
/// <summary>
|
||||
/// this basically opens Snooper, survives it crashing, and opens it again without taking FModel down with it.
|
||||
/// clauded but fixes real problems I had with simpler implementations
|
||||
///
|
||||
/// The simple version (a lazy window on a throwaway thread) does not work. A crash left FModel unable to open its
|
||||
/// own menu items, and Snooper could not be started again without restarting FModel. Both come from the thread
|
||||
/// dying: GLFW and the GL driver keep per-thread state there. So the thread Snooper is created on never dies until
|
||||
/// FModel itself closes, it just loops back to waiting for the next window to build.
|
||||
/// </summary>
|
||||
public sealed class SnooperHost(Func<EditorWindow> build) : IDisposable
|
||||
{
|
||||
private readonly Lock _lock = new();
|
||||
private readonly SemaphoreSlim _wanted = new(0);
|
||||
private readonly ManualResetEventSlim _ready = new();
|
||||
|
||||
private volatile EditorWindow? _window;
|
||||
private Exception? _failure;
|
||||
private Thread? _thread;
|
||||
|
||||
public EditorWindow Window
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_window is { } current) return current;
|
||||
|
||||
_thread ??= StartThread();
|
||||
_ready.Reset();
|
||||
_wanted.Release();
|
||||
_ready.Wait();
|
||||
|
||||
return _window ?? throw new InvalidOperationException("Snooper failed to start", _failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Thread StartThread()
|
||||
{
|
||||
// this thread must never die until fmodel closes
|
||||
// if it dies first, opening any menu hangs the ui thread inside UiaReturnRawElementProvider, which is
|
||||
// wpf building the popup, and exiting hangs too (dead thread leaves com state behind???)
|
||||
var thread = new Thread(RenderLoop) { IsBackground = true, Name = "Snooper" };
|
||||
thread.Start();
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
private void RenderLoop()
|
||||
{
|
||||
using var _ = LogContext.PushProperty("SourceContext", "Snooper");
|
||||
GLFWProvider.CheckForMainThread = false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
_wanted.Wait();
|
||||
|
||||
try
|
||||
{
|
||||
_failure = null;
|
||||
_window = build();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_failure = e;
|
||||
continue;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ready.Set();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_window?.Run();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Fatal(e, "Crashed");
|
||||
FLogger.Append(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_window?.Dispose();
|
||||
_window = null; // the next caller asks for a new one, on this same thread
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_window is not { } window) return;
|
||||
|
||||
window.Shutdown();
|
||||
SpinWait.SpinUntil(() => _window == null, TimeSpan.FromSeconds(10));
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ using System.Windows.Threading;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Options;
|
||||
using CUE4Parse.Utils;
|
||||
using Editor;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
@@ -144,7 +145,7 @@ public class ExportSessionViewModel : ViewModel
|
||||
|
||||
private ExportSessionViewModel()
|
||||
{
|
||||
// ImGuiSink.Instance.OnExporterLogEvent += OnLogEvent;
|
||||
ImGuiSink.Instance.OnExporterLogEvent += OnLogEvent;
|
||||
}
|
||||
|
||||
private void OnLogEvent(LogEvent log)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
@@ -11,8 +10,6 @@ using CUE4Parse.UE4.Objects.Engine;
|
||||
using Editor;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using Serilog;
|
||||
using Snooper.Rendering.Actors;
|
||||
using Snooper.Rendering.Components;
|
||||
using Snooper.Rendering.Components.Light;
|
||||
@@ -25,99 +22,65 @@ public class SnooperViewModel : ViewModel, IDisposable
|
||||
{
|
||||
public static SnooperViewModel Instance { get; } = new();
|
||||
|
||||
private readonly Lazy<EditorWindow> _editor;
|
||||
private readonly SnooperHost _host;
|
||||
|
||||
private SnooperViewModel()
|
||||
{
|
||||
var scale = GetDpiScale();
|
||||
var htz = GetMaxRefreshFrequency();
|
||||
var width = Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenWidth * .75 * scale);
|
||||
var width = Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenWidth * .9 * scale);
|
||||
var height = Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenHeight * .85 * scale);
|
||||
|
||||
_editor = new Lazy<EditorWindow>(() => StartEditor(htz, width, height));
|
||||
}
|
||||
|
||||
private static EditorWindow StartEditor(int htz, int width, int height)
|
||||
{
|
||||
GLFWProvider.CheckForMainThread = false;
|
||||
|
||||
var ready = new ManualResetEventSlim();
|
||||
EditorWindow? editor = null;
|
||||
Exception? failure = null;
|
||||
|
||||
new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
editor = new EditorWindow(htz, width, height, ApplicationService.ApplicationView.CUE4Parse.Provider, false, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
failure = e;
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ready.Set();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
editor.Run();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Snooper crashed");
|
||||
}
|
||||
}) { IsBackground = true, Name = "Snooper" }.Start();
|
||||
|
||||
ready.Wait();
|
||||
return editor ?? throw new InvalidOperationException("Snooper failed to start", failure);
|
||||
_host = new SnooperHost(() => new EditorWindow(htz, width, height, ApplicationService.ApplicationView.CUE4Parse.Provider, false, true));
|
||||
}
|
||||
|
||||
public void Load(UObject? obj)
|
||||
{
|
||||
var scene = new Actor("Example Scene");
|
||||
Actor? actor = obj switch
|
||||
{
|
||||
UStaticMesh sm => new MeshActor(sm),
|
||||
USkeletalMesh sk => new MeshActor(sk),
|
||||
UWorld w => new WorldActor(w),
|
||||
_ => null
|
||||
};
|
||||
|
||||
var editor = _host.Window;
|
||||
editor.Invoke(() =>
|
||||
{
|
||||
if (editor.Manager.RootActor == null)
|
||||
editor.Manager.LoadScene(CreateScene(obj is UWorld));
|
||||
|
||||
if (actor != null)
|
||||
editor.Manager.LoadScene(actor);
|
||||
|
||||
editor.Show();
|
||||
});
|
||||
}
|
||||
|
||||
private Actor CreateScene(bool transparentGrid)
|
||||
{
|
||||
var scene = new Actor("Scene");
|
||||
scene.Components.Add(new BoxComponent(Vector3.Zero, Vector3.One));
|
||||
|
||||
var grid = new Actor("Grid");
|
||||
grid.Components.Add(transparentGrid ? new GridComponent() : new OpaqueGridComponent());
|
||||
scene.Children.Add(grid);
|
||||
|
||||
var camera = new CameraActor("Camera");
|
||||
camera.CameraComponent.LocalTransform.Position = new Vector3(1, 2, -0.5f);
|
||||
camera.CameraComponent.LocalTransform.Rotation = new Quaternion(0, -1, 0, 1);
|
||||
scene.Children.Add(camera);
|
||||
|
||||
var sun = new Actor("Sun Light");
|
||||
sun.Components.Add(new DirectionalLightComponent(MathF.PI, new Vector3(1.0f, 0.87f, 0.72f), new Transform(new Quaternion(new Vector3(0.5f, -0.5f, 0.0f), 1.0f)), "Directional Light"));
|
||||
sun.Components.Add(new DirectionalLightComponent(MathF.PI, new Vector3(1.0f, 0.87f, 0.72f), new Transform(new Vector3(16.5f, 0, 0), new Quaternion(new Vector3(0.5f, -0.5f, 0.0f), 1.0f)), "Directional Light"));
|
||||
scene.Children.Add(sun);
|
||||
|
||||
GridComponent gridComponent = new OpaqueGridComponent();
|
||||
switch (obj)
|
||||
{
|
||||
case UStaticMesh sm:
|
||||
scene.Children.Add(new MeshActor(sm));
|
||||
break;
|
||||
case USkeletalMesh sk:
|
||||
scene.Children.Add(new MeshActor(sk));
|
||||
break;
|
||||
case UWorld w:
|
||||
gridComponent = new GridComponent();
|
||||
scene.Children.Add(new WorldActor(w));
|
||||
break;
|
||||
}
|
||||
var grid = new Actor("Grid");
|
||||
grid.Components.Add(gridComponent);
|
||||
scene.Children.Insert(0, grid);
|
||||
|
||||
var editor = _editor.Value;
|
||||
editor.Invoke(() =>
|
||||
{
|
||||
editor.Manager.LoadScene(scene);
|
||||
editor.Show();
|
||||
});
|
||||
return scene;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
var editor = _editor.Value;
|
||||
var editor = _host.Window;
|
||||
editor.Invoke(editor.Show);
|
||||
}
|
||||
|
||||
@@ -189,9 +152,5 @@ public class SnooperViewModel : ViewModel, IDisposable
|
||||
return rf;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_editor.IsValueCreated) return;
|
||||
_editor.Value.Dispose();
|
||||
}
|
||||
public void Dispose() => _host.Dispose();
|
||||
}
|
||||
|
||||
2
Snooper
2
Snooper
Submodule Snooper updated: b6ce504513...5c3e4fcc89
Reference in New Issue
Block a user