Skip to content

Commit

Permalink
Fix: UI crash on app exit and refactor: UI _drawAction became a method (
Browse files Browse the repository at this point in the history
#567)

* UI: Fix crash on app exit with Dispose

Signed-off-by: Maximilien Noal <noal.maximilien@gmail.com>

* refactor: replaced _drawAction with method

Signed-off-by: Maximilien Noal <noal.maximilien@gmail.com>

* renamed PR github workflow

Signed-off-by: Maximilien Noal <noal.maximilien@gmail.com>

* UI: Initialize timer once

Signed-off-by: Maximilien Noal <noal.maximilien@gmail.com>

* UI: set _disposed to true early

Signed-off-by: Maximilien Noal <noal.maximilien@gmail.com>

---------

Signed-off-by: Maximilien Noal <noal.maximilien@gmail.com>
  • Loading branch information
maximilien-noal authored Jan 24, 2024
1 parent 7f99dfd commit 5a3a20a
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 32 deletions.
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: 33 additions & 31 deletions src/Spice86/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ public sealed partial class MainWindowViewModel : ViewModelBase, IPauseStatus, I
[ObservableProperty]
private Configuration _configuration;
private bool _disposed;
private bool _renderingTimerInitialized;
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 +138,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 +260,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 +332,30 @@ 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;
}
_drawAction = () => {
_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 {
_renderingTimerInitialized = true;
_drawTimer.Elapsed += (_, _) => DrawScreen();
_drawTimer.Start();
}

private void DrawScreen() {
if (_disposed || _isSettingResolution || _isAppClosing || _uiUpdateMethod is null || Bitmap is null || RenderScreen is null) {
return;
}
_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 {
if (!_disposed) {
_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 +436,21 @@ public void SetResolution(int width, int height) => _uiDispatcher.Post(() => {
if (Width != width || Height != height) {
Width = width;
Height = height;
if (_disposed) {
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 (!_disposed) {
_drawingSemaphoreSlim?.Release();
}
}
}
_isSettingResolution = false;
InitializeRenderingThread();
InitializeRenderingTimer();
}, DispatcherPriority.MaxValue);

public event EventHandler<UIRenderEventArgs>? RenderScreen;
Expand All @@ -455,8 +463,11 @@ public void Dispose() {

private void Dispose(bool disposing) {
if (!_disposed) {
_disposed = true;
if (disposing) {
DisposeDrawThread();
_windowActivator.CloseDebugWindow();
_drawTimer.Stop();
_drawTimer.Dispose();
_uiDispatcher.Post(() => {
Bitmap?.Dispose();
Cursor?.Dispose();
Expand All @@ -465,22 +476,13 @@ private void Dispose(bool disposing) {
PlayCommand.Execute(null);
IsMachineRunning = false;
DisposeEmulator();
_windowActivator.CloseDebugWindow();
if (_emulatorThread?.IsAlive == true) {
_emulatorThread.Join();
}
}
_disposed = true;
}
}

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

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

[RelayCommand]
Expand Down

0 comments on commit 5a3a20a

Please sign in to comment.