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(); }
}
}
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(); }
}
}