Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: UI crash on app exit and refactor: UI _drawAction became a method #567

Merged
merged 5 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Test and Publish NuGet
name: Run tests

# Trigger the workflow on PRs
on:
Expand Down
64 changes: 35 additions & 29 deletions src/Spice86/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,15 @@ public sealed partial class MainWindowViewModel : ViewModelBase, IPauseStatus, I
[ObservableProperty]
private Configuration _configuration;
private bool _disposed;
private bool _renderingTimerInitialized;
private bool _drawingSemaphoreSlimDisposed;
private Thread? _emulatorThread;
private bool _isSettingResolution;
private string _lastExecutableDirectory = string.Empty;
private bool _closeAppOnEmulatorExit;
private bool _isAppClosing;

private static Action? _uiUpdateMethod;
private Action? _drawAction;
private readonly Timer _drawTimer = new(1000.0 / ScreenRefreshHz);
private readonly SemaphoreSlim? _drawingSemaphoreSlim = new(1, 1);

Expand Down Expand Up @@ -138,8 +139,6 @@ public double Scale {
set => SetProperty(ref _scale, Math.Max(value, 1));
}

private bool _isDrawThreadInitialized;

[ObservableProperty]
private Cursor? _cursor = Cursor.Default;

Expand Down Expand Up @@ -262,7 +261,7 @@ private async Task StartNewExecutable(string? filePath = null) {
File.Exists(filePath)) {
await RestartEmulatorWithNewProgram(filePath);
}
else if (_hostStorageProvider.CanOpen == true) {
else if (_hostStorageProvider.CanOpen) {
FilePickerOpenOptions options = new() {
Title = "Start Executable...",
AllowMultiple = false,
Expand Down Expand Up @@ -334,25 +333,32 @@ public void ShowColorPalette() {
[RelayCommand]
public void ResetTimeMultiplier() => TimeMultiplier = Configuration.TimeMultiplier;

private void InitializeRenderingThread() {
if (_isDrawThreadInitialized || _disposed || _isSettingResolution || _isAppClosing || _uiUpdateMethod is null || Bitmap is null || RenderScreen is null) {
private void InitializeRenderingTimer() {
if (_renderingTimerInitialized) {
return;
}
_renderingTimerInitialized = true;
_drawTimer.Elapsed += (_, _) => DrawScreen();
_drawTimer.Start();
}

private void DrawScreen() {
if (_disposed || _isSettingResolution || _isAppClosing || _uiUpdateMethod is null || Bitmap is null || RenderScreen is null) {
return;
}
_drawAction = () => {
if (!_drawingSemaphoreSlimDisposed) {
_drawingSemaphoreSlim?.Wait();
try {
using ILockedFramebuffer pixels = Bitmap.Lock();
var uiRenderEventArgs = new UIRenderEventArgs(pixels.Address, pixels.RowBytes * pixels.Size.Height / 4);
RenderScreen.Invoke(this, uiRenderEventArgs);
} finally {
}
try {
using ILockedFramebuffer pixels = Bitmap.Lock();
var uiRenderEventArgs = new UIRenderEventArgs(pixels.Address, pixels.RowBytes * pixels.Size.Height / 4);
RenderScreen.Invoke(this, uiRenderEventArgs);
} finally {
if (!_drawingSemaphoreSlimDisposed) {
_drawingSemaphoreSlim?.Release();
}
_uiDispatcher.Post(static () => _uiUpdateMethod.Invoke(), DispatcherPriority.Render);
};

_drawTimer.Elapsed += (_, _) => _drawAction.Invoke();
_drawTimer.Start();
_isDrawThreadInitialized = true;
}
_uiDispatcher.Post(static () => _uiUpdateMethod.Invoke(), DispatcherPriority.Render);
}

public double MouseX { get; set; }
Expand Down Expand Up @@ -433,16 +439,21 @@ public void SetResolution(int width, int height) => _uiDispatcher.Post(() => {
if (Width != width || Height != height) {
Width = width;
Height = height;
if (_drawingSemaphoreSlimDisposed) {
return;
}
_drawingSemaphoreSlim?.Wait();
try {
Bitmap?.Dispose();
Bitmap = new WriteableBitmap(new PixelSize(Width, Height), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Opaque);
} finally {
_drawingSemaphoreSlim?.Release();
if (!_drawingSemaphoreSlimDisposed) {
_drawingSemaphoreSlim?.Release();
}
}
}
_isSettingResolution = false;
InitializeRenderingThread();
InitializeRenderingTimer();
}, DispatcherPriority.MaxValue);

public event EventHandler<UIRenderEventArgs>? RenderScreen;
Expand All @@ -456,16 +467,18 @@ public void Dispose() {
private void Dispose(bool disposing) {
if (!_disposed) {
if (disposing) {
DisposeDrawThread();
_windowActivator.CloseDebugWindow();
_drawTimer.Stop();
_drawTimer.Dispose();
_uiDispatcher.Post(() => {
Bitmap?.Dispose();
Cursor?.Dispose();
}, DispatcherPriority.MaxValue);
_drawingSemaphoreSlimDisposed = true;
_drawingSemaphoreSlim?.Dispose();
PlayCommand.Execute(null);
IsMachineRunning = false;
DisposeEmulator();
_windowActivator.CloseDebugWindow();
if (_emulatorThread?.IsAlive == true) {
_emulatorThread.Join();
}
Expand All @@ -474,13 +487,6 @@ private void Dispose(bool disposing) {
}
}

private void DisposeDrawThread() {
_drawAction = null;
_drawTimer.Stop();
_drawTimer.Dispose();
_isDrawThreadInitialized = false;
}

private void DisposeEmulator() => _programExecutor?.Dispose();

[RelayCommand]
Expand Down
Loading