改了一亿个东西,修了一亿个 BUG
This commit is contained in:
48
Utils/CancelManager.cs
Normal file
48
Utils/CancelManager.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace AnotherReplayReader.Utils
|
||||
{
|
||||
internal class CancelManager : IDisposable
|
||||
{
|
||||
private CancellationToken _linkedToken;
|
||||
private CancellationTokenSource? _source;
|
||||
|
||||
public CancellationToken Token => Materialize().Token;
|
||||
|
||||
public void Reset(CancellationToken linked)
|
||||
{
|
||||
_linkedToken = linked;
|
||||
if (_source is { } source)
|
||||
{
|
||||
_source = null;
|
||||
try
|
||||
{
|
||||
source.Cancel();
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"Cancellation failed: {e}";
|
||||
}
|
||||
source.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public CancellationToken ResetAndGetToken(CancellationToken linked)
|
||||
{
|
||||
Reset(linked);
|
||||
return Token;
|
||||
}
|
||||
|
||||
public void Dispose() => Reset(default);
|
||||
|
||||
private CancellationTokenSource Materialize()
|
||||
{
|
||||
if (_source is null)
|
||||
{
|
||||
_source = CancellationTokenSource.CreateLinkedTokenSource(_linkedToken);
|
||||
}
|
||||
return _source;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Utils/CancellableTaskExtensions.cs
Normal file
23
Utils/CancellableTaskExtensions.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AnotherReplayReader.Utils
|
||||
{
|
||||
internal static class CancellableTaskExtensions
|
||||
{
|
||||
public static async Task IgnoreCancel(this Task task)
|
||||
{
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
}
|
||||
|
||||
public static void Forget(this Task task)
|
||||
{
|
||||
const TaskContinuationOptions flags = TaskContinuationOptions.NotOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously;
|
||||
task.ContinueWith(t => t.Exception?.Handle(_ => true), flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Utils/ImmutableArrayExtensions.cs
Normal file
20
Utils/ImmutableArrayExtensions.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace AnotherReplayReader.Utils
|
||||
{
|
||||
static class ImmutableArrayExtensions
|
||||
{
|
||||
public static int? FindIndex<T>(this in ImmutableArray<T> a, Predicate<T> p)
|
||||
{
|
||||
for (var i = 0; i < a.Length; ++i)
|
||||
{
|
||||
if (p(a[i]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Utils/Lock.cs
Normal file
32
Utils/Lock.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace AnotherReplayReader.Utils
|
||||
{
|
||||
internal class Lock : IDisposable
|
||||
{
|
||||
public readonly object LockObject;
|
||||
private bool _disposed = false;
|
||||
|
||||
public Lock(object lockObject)
|
||||
{
|
||||
LockObject = lockObject;
|
||||
Monitor.Enter(LockObject);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Monitor.Exit(LockObject);
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static T Run<T>(object @lock, Func<T> action)
|
||||
{
|
||||
using var locker = new Lock(@lock);
|
||||
return action();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Utils/PinyinExtensions.cs
Normal file
27
Utils/PinyinExtensions.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using NPinyin;
|
||||
using System;
|
||||
|
||||
namespace AnotherReplayReader.Utils
|
||||
{
|
||||
static class PinyinExtensions
|
||||
{
|
||||
public static bool ContainsIgnoreCase(this string self, string? s)
|
||||
{
|
||||
return s != null && self.IndexOf(s, StringComparison.CurrentCultureIgnoreCase) != -1;
|
||||
}
|
||||
|
||||
public static string? ToPinyin(this string self)
|
||||
{
|
||||
string pinyin;
|
||||
try
|
||||
{
|
||||
pinyin = Pinyin.GetPinyin(self);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return pinyin.Replace(" ", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Utils/RegistryUtils.cs
Normal file
28
Utils/RegistryUtils.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
|
||||
namespace AnotherReplayReader.Utils
|
||||
{
|
||||
internal static class RegistryUtils
|
||||
{
|
||||
public static string? Retrieve(RegistryHive hive, string path, string value)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var view32 = RegistryKey.OpenBaseKey(hive, RegistryView.Registry32);
|
||||
using var ra3Key = view32.OpenSubKey(path, false);
|
||||
return ra3Key?.GetValue(value) as string;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"Failed to retrieve registy {hive}:{path}:{value}: {e}";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string? RetrieveInRa3(RegistryHive hive, string value)
|
||||
{
|
||||
return Retrieve(hive, @"Software\Electronic Arts\Electronic Arts\Red Alert 3", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Utils/ReplayPinyinList.cs
Normal file
61
Utils/ReplayPinyinList.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using AnotherReplayReader.ReplayFile;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace AnotherReplayReader.Utils
|
||||
{
|
||||
internal class ReplayPinyinList
|
||||
{
|
||||
private readonly PlayerIdentity _playerIdentity;
|
||||
public ImmutableArray<Replay> Replays { get; } = ImmutableArray<Replay>.Empty;
|
||||
public ImmutableArray<ReplayPinyinData> Pinyins { get; } = ImmutableArray<ReplayPinyinData>.Empty;
|
||||
|
||||
public ReplayPinyinList(PlayerIdentity playerIdentity) :
|
||||
this(ImmutableArray<Replay>.Empty, playerIdentity)
|
||||
{
|
||||
}
|
||||
|
||||
public ReplayPinyinList(ImmutableArray<Replay> replay, PlayerIdentity playerIdentity) :
|
||||
this(replay,
|
||||
replay.Select(replay => new ReplayPinyinData(replay, playerIdentity)).ToImmutableArray(),
|
||||
playerIdentity)
|
||||
{
|
||||
}
|
||||
|
||||
private ReplayPinyinList(ImmutableArray<Replay> replay,
|
||||
ImmutableArray<ReplayPinyinData> pinyins,
|
||||
PlayerIdentity playerIdentity)
|
||||
{
|
||||
_playerIdentity = playerIdentity;
|
||||
Replays = replay;
|
||||
Pinyins = pinyins;
|
||||
}
|
||||
|
||||
public ReplayPinyinList SetItem(int index, Replay replay)
|
||||
{
|
||||
return new(Replays.SetItem(index, replay),
|
||||
Pinyins.SetItem(index, new(replay, _playerIdentity)),
|
||||
_playerIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
class ReplayPinyinData
|
||||
{
|
||||
public Replay Replay { get; }
|
||||
public string? PinyinDetails { get; }
|
||||
public string? PinyinMod { get; }
|
||||
|
||||
public ReplayPinyinData(Replay replay, PlayerIdentity playerIdentity)
|
||||
{
|
||||
Replay = replay;
|
||||
PinyinDetails = replay.GetDetails(playerIdentity).ToPinyin();
|
||||
PinyinMod = replay.Mod.ModName.ToPinyin();
|
||||
}
|
||||
|
||||
public bool MatchPinyin(string? pinyin)
|
||||
{
|
||||
return PinyinDetails?.ContainsIgnoreCase(pinyin) is true
|
||||
|| PinyinMod?.ContainsIgnoreCase(pinyin) is true;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Utils/ShortTimeSpan.cs
Normal file
28
Utils/ShortTimeSpan.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AnotherReplayReader.Utils
|
||||
{
|
||||
public readonly struct ShortTimeSpan : IEquatable<ShortTimeSpan>, IComparable<ShortTimeSpan>, IComparable
|
||||
{
|
||||
public readonly TimeSpan Value;
|
||||
|
||||
public ShortTimeSpan(TimeSpan value) => Value = value;
|
||||
public static implicit operator TimeSpan(ShortTimeSpan span) => span.Value;
|
||||
public static implicit operator ShortTimeSpan(TimeSpan value) => new(value);
|
||||
public override string ToString() => $"{(int)Value.TotalMinutes:00}:{Value.Seconds:00}";
|
||||
|
||||
public int CompareTo(ShortTimeSpan other) => Value.CompareTo(other.Value);
|
||||
public int CompareTo(object obj) => obj is ShortTimeSpan span ? CompareTo(span) : 1;
|
||||
public override bool Equals(object? obj) => obj is ShortTimeSpan span && Equals(span);
|
||||
public bool Equals(ShortTimeSpan other) => Value.Equals(other.Value);
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
public static bool operator ==(ShortTimeSpan left, ShortTimeSpan right) => left.Equals(right);
|
||||
public static bool operator !=(ShortTimeSpan left, ShortTimeSpan right) => !(left == right);
|
||||
public static bool operator <(ShortTimeSpan left, ShortTimeSpan right) => left.CompareTo(right) < 0;
|
||||
public static bool operator >(ShortTimeSpan left, ShortTimeSpan right) => left.CompareTo(right) > 0;
|
||||
}
|
||||
}
|
||||
30
Utils/TaskQueue.cs
Normal file
30
Utils/TaskQueue.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace AnotherReplayReader.Utils
|
||||
{
|
||||
internal class TaskQueue
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly Dispatcher _dispatcher;
|
||||
private Task _current = Task.CompletedTask;
|
||||
|
||||
public TaskQueue(Dispatcher dispatcher)
|
||||
{
|
||||
_dispatcher = dispatcher;
|
||||
}
|
||||
|
||||
public Task Enqueue(Func<Task> getTask, CancellationToken cancelToken)
|
||||
{
|
||||
using var locker = new Lock(_lock);
|
||||
_current = _current.ContinueWith(async t =>
|
||||
{
|
||||
await _dispatcher.InvokeAsync(getTask, DispatcherPriority.Background, cancelToken);
|
||||
}, cancelToken);
|
||||
return _current.IgnoreCancel();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user