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

自動保存 #900

Merged
merged 15 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
CommandRecorderでGetStorablesをキャッシュするようにした
  • Loading branch information
yuto-trd committed Jan 22, 2024
commit 655282040b064a57318a0439f9fd3fe2fe784969
149 changes: 112 additions & 37 deletions src/Beutl.Core/CommandRecorder.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Collections.Immutable;
using System.ComponentModel;

using Beutl.Services;
using Beutl.Language;
using Beutl.Services;

using Microsoft.Extensions.Logging;

Expand All @@ -17,19 +17,22 @@ public enum CommandType
Redo,
}

internal record Entry(IRecordableCommand Command, ImmutableHashSet<IStorable> Storables);

public class CommandRecorder : INotifyPropertyChanged
{
public static readonly CommandRecorder Default = new();
private static readonly PropertyChangedEventArgs s_canUndoArgs = new(nameof(CanUndo));
private static readonly PropertyChangedEventArgs s_canRedoArgs = new(nameof(CanRedo));
private static readonly PropertyChangedEventArgs s_lastExecutedTimeArgs = new(nameof(LastExecutedTime));
private static readonly ILogger<CommandRecorder> s_logger = BeutlApplication.Current.LoggerFactory.CreateLogger<CommandRecorder>();
private readonly RingStack<IRecordableCommand> _undoStack = new(20000);
private readonly RingStack<IRecordableCommand> _redoStack = new(20000);
private bool _isExecuting;
private readonly ILogger<CommandRecorder> _logger = BeutlApplication.Current.LoggerFactory.CreateLogger<CommandRecorder>();
private readonly RingStack<Entry> _undoStack = new(20000);
private readonly RingStack<Entry> _redoStack = new(20000);
private readonly SemaphoreSlim _semaphoreSlim = new(1);
private bool _canUndo;
private bool _canRedo;
private DateTime _lastExecutedTime;
private IRecordableCommand? _executingCommand;

public DateTime LastExecutedTime
{
Expand Down Expand Up @@ -78,104 +81,176 @@ private set

public event EventHandler? Cleared;

private static Entry CreateEntry(IRecordableCommand command)
{
return new Entry(command, command.GetStorables().Where(v => v != null).ToImmutableHashSet()!);
}

private Entry? CreateEntryAndCheck(IRecordableCommand command)
{
if (command.Nothing)
{
_logger.LogInformation("IRecordableCommand.Nothing is True. (Type: {Type})", command.GetType());
return null;
}

Entry entry = CreateEntry(command);
if (entry.Storables.Count == 0)
{
_logger.LogWarning("Storables.Count is 0. (Type: {Type})", command.GetType());
}

return entry;
}

private bool SemaphoreWait()
{
if (!_semaphoreSlim.Wait(1000))
{
_logger.LogWarning("SemaphoreSlim timeout. (ExecutingCommand: {Command})", _executingCommand);
NotificationService.ShowError(string.Empty, Message.OperationCouldNotBeExecuted);
Canceled?.Invoke(null, EventArgs.Empty);
return false;
}
else
{
return true;
}
}

public void PushOnly(IRecordableCommand command)
{
_undoStack.Push(command);
CanUndo = _undoStack.Count > 0;
if (!SemaphoreWait())
{
return;
}

_redoStack.Clear();
CanRedo = _redoStack.Count > 0;
try
{
if (CreateEntryAndCheck(command) is not { } entry)
return;

LastExecutedTime = DateTime.UtcNow;
Executed?.Invoke(command, new(command, CommandType.Do));
_undoStack.Push(entry);
CanUndo = _undoStack.Count > 0;

_redoStack.Clear();
CanRedo = _redoStack.Count > 0;

LastExecutedTime = DateTime.UtcNow;
Executed?.Invoke(command, new(command, CommandType.Do));
}
finally
{
_semaphoreSlim.Release();
}
}

public void DoAndPush(IRecordableCommand command)
{
if (_isExecuting)
if (!SemaphoreWait())
{
Debug.WriteLine("if (!process) {...");
return;
}

try
{
_isExecuting = true;
if (CreateEntryAndCheck(command) is not { } entry)
return;

_executingCommand = command;
command.Do();

_undoStack.Push(command);
_undoStack.Push(entry);
CanUndo = _undoStack.Count > 0;

_redoStack.Clear();
CanRedo = _redoStack.Count > 0;
}
catch (Exception ex)
{
s_logger.LogError(ex, "An exception occurred while executing the command. {Command}", command);
_logger.LogError(ex, "An exception occurred while executing the command. {Command}", command);
NotificationService.ShowError(string.Empty, Message.OperationCouldNotBeExecuted);
Canceled?.Invoke(null, EventArgs.Empty);
}
finally
{
_semaphoreSlim.Release();
_executingCommand = null;
}

_isExecuting = false;
LastExecutedTime = DateTime.UtcNow;
Executed?.Invoke(command, new(command, CommandType.Do));
}

public void Undo()
{
if (_isExecuting) return;

if (_undoStack.Count >= 1)
{
IRecordableCommand command = _undoStack.Pop();
if (!SemaphoreWait())
{
return;
}

Entry entry = _undoStack.Pop();
CanUndo = _undoStack.Count > 0;

try
{
_isExecuting = true;
command.Undo();
_executingCommand = entry.Command;
entry.Command.Undo();

_redoStack.Push(command);
_redoStack.Push(entry);
CanRedo = _redoStack.Count > 0;
}
catch (Exception ex)
{
s_logger.LogError(ex, "An exception occurred while executing the undo command. {Command}", command);
_logger.LogError(ex, "An exception occurred while executing the undo command. {Command}", entry.Command);
NotificationService.ShowError(string.Empty, Message.OperationCouldNotBeExecuted);
}
finally
{
_semaphoreSlim.Release();
_executingCommand = null;
}

_isExecuting = false;
LastExecutedTime = DateTime.UtcNow;
Executed?.Invoke(command, new(command, CommandType.Undo));
Executed?.Invoke(entry.Command, new(entry.Command, CommandType.Undo));
}
}

public void Redo()
{
if (_isExecuting) return;

if (_redoStack.Count >= 1)
{
IRecordableCommand command = _redoStack.Pop();
if (!SemaphoreWait())
{
return;
}

Entry entry = _redoStack.Pop();
CanRedo = _redoStack.Count > 0;

try
{
_isExecuting = true;
command.Redo();
_executingCommand = entry.Command;
entry.Command.Redo();

_undoStack.Push(command);
_undoStack.Push(entry);
CanUndo = _undoStack.Count > 0;
}
catch (Exception ex)
{
s_logger.LogError(ex, "An exception occurred while executing the redo command. {Command}", command);
_logger.LogError(ex, "An exception occurred while executing the redo command. {Command}", entry.Command);
NotificationService.ShowError(string.Empty, Message.OperationCouldNotBeExecuted);
}
finally
{
_semaphoreSlim.Release();
_executingCommand = null;
}

_isExecuting = false;
LastExecutedTime = DateTime.UtcNow;
Executed?.Invoke(command, new(command, CommandType.Redo));
Executed?.Invoke(entry.Command, new(entry.Command, CommandType.Redo));
}
}

Expand Down
3 changes: 0 additions & 3 deletions src/Beutl.Core/IRecordableCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ private sealed class WithStoableCommand(
{
return command.GetStorables()
.Concat(storables)
.Distinct()
.ToImmutableArray();
}
}
Expand Down Expand Up @@ -112,7 +111,6 @@ public bool Nothing
{
return command1.GetStorables()
.Concat(command2.GetStorables())
.Distinct()
.ToImmutableArray();
}

Expand Down Expand Up @@ -167,7 +165,6 @@ public bool Nothing
{
return commands.SelectMany(v => v.GetStorables())
.Concat(storables)
.Distinct()
.ToImmutableArray();
}

Expand Down