XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEWarframe

【WinUI】基于Warframe游戏本体的公开服务器以及安装器

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Collections.ObjectModel;
using System.ComponentModel;
using Newtonsoft.Json.Linq;

namespace XFEWarframe.Desktop.Editing;

// Read-only previews never save a fingerprint. A changed input invalidates any pending response.
internal sealed class RivenPreview
{
    private readonly ObservableCollection<RivenStatRow> rows;
    private readonly Func<bool> canPreview;
    private readonly Func<JObject> request;
    private readonly Func<JObject, Task<JObject>> calculate;
    private readonly Action<JObject> apply;
    private readonly HashSet<RivenStatRow> observed = [];
    private CancellationTokenSource? pending;

    public RivenPreview(INotifyPropertyChanged context, ObservableCollection<RivenStatRow> rows, Func<bool> canPreview,
        Func<JObject> request, Func<JObject, Task<JObject>> calculate, Action<JObject> apply)
    {
        this.rows = rows; this.canPreview = canPreview; this.request = request; this.calculate = calculate; this.apply = apply;
        context.PropertyChanged += (_, e) =>
        {
            if (e.PropertyName is "IsBusy") Schedule(clear: false);
            else if (e.PropertyName is "Family" or "Weapon" or "Disposition" or "Level" or "Mastery" or "Rerolls" or "Polarity" or "Veiled") Schedule();
        };
        rows.CollectionChanged += (_, _) =>
        {
            foreach (var row in observed.Where(x => !rows.Contains(x)).ToArray()) { row.PropertyChanged -= RowChanged; observed.Remove(row); }
            foreach (var row in rows) if (observed.Add(row)) row.PropertyChanged += RowChanged;
            Schedule();
        };
    }
    private void RowChanged(object? sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName is nameof(RivenStatRow.SelectedTag) or nameof(RivenStatRow.Negative) or nameof(RivenStatRow.ModeIndex) or nameof(RivenStatRow.Value)) Schedule();
    }
    private void Schedule(bool clear = true)
    {
        pending?.Cancel();
        if (clear) foreach (var row in rows) row.ResetResult();
        if (!canPreview()) return;
        var cancellation = pending = new CancellationTokenSource();
        _ = RefreshAsync(cancellation);
    }
    private async Task RefreshAsync(CancellationTokenSource cancellation)
    {
        var token = cancellation.Token;
        try
        {
            await Task.Delay(200, token);
            var response = await calculate(request()).WaitAsync(token);
            if (!token.IsCancellationRequested) apply(response);
        }
        catch (OperationCanceledException) when (token.IsCancellationRequested) { }
        catch (Exception error)
        {
            if (!token.IsCancellationRequested) foreach (var row in rows) row.Result = UiText.Translate(error.Message);
        }
        finally { if (ReferenceEquals(pending, cancellation)) pending = null; cancellation.Dispose(); }
    }
}