| 1 | using System.Collections.ObjectModel; |
| 2 | using Microsoft.UI.Xaml; |
| 3 | using Microsoft.UI.Xaml.Controls; |
| 4 | using RblxTool.Models; |
| 5 | using RblxTool.Services; |
| 6 | |
| 7 | namespace RblxTool.Views; |
| 8 | |
| 9 | public sealed partial class SweepPage : Page |
| 10 | { |
| 11 | private const int MaxFeedLines = 4000; |
| 12 | |
| 13 | private readonly ObservableCollection<FeedLine> _feed = new(); |
| 14 | private JobRunner? _runner; |
| 15 | private DispatcherTimer? _metricsTimer; |
| 16 | |
| 17 | public SweepPage() |
| 18 | { |
| 19 | InitializeComponent(); |
| 20 | FeedList.ItemsSource = _feed; |
| 21 | Loaded += OnLoaded; |
| 22 | Unloaded += OnUnloaded; |
| 23 | } |
| 24 | |
| 25 | private void OnLoaded(object sender, RoutedEventArgs e) |
| 26 | { |
| 27 | _runner = AppState.Current.CreateRunner(DispatcherQueue); |
| 28 | _runner.EventReceived += OnEvent; |
| 29 | _runner.Completed += OnCompleted; |
| 30 | _metricsTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(750) }; |
| 31 | _metricsTimer.Tick += (_, _) => RefreshMetrics(); |
| 32 | } |
| 33 | |
| 34 | private void OnUnloaded(object sender, RoutedEventArgs e) |
| 35 | { |
| 36 | _metricsTimer?.Stop(); |
| 37 | _runner?.Stop(); |
| 38 | } |
| 39 | |
| 40 | private void OnStart(object sender, RoutedEventArgs e) |
| 41 | { |
| 42 | if (AppState.Current.Active is null) |
| 43 | { |
| 44 | Append(new FeedLine(" select an account first", FeedTone.Bad)); |
| 45 | return; |
| 46 | } |
| 47 | var request = new SweepRequest |
| 48 | { |
| 49 | MaxPrice = (ulong)Math.Max(0, MaxPriceBox.Value), |
| 50 | Pages = (int)Math.Max(1, PagesBox.Value), |
| 51 | Limit = (int)Math.Max(0, LimitBox.Value), |
| 52 | ScanThreads = (int)Math.Max(1, ScanThreadsBox.Value), |
| 53 | DetailThreads = (int)Math.Max(1, DetailThreadsBox.Value), |
| 54 | BuyThreads = (int)Math.Max(1, BuyThreadsBox.Value), |
| 55 | Buy = BuyToggle.IsOn, |
| 56 | DryRun = DryRunToggle.IsOn, |
| 57 | SkipOwned = SkipOwnedToggle.IsOn, |
| 58 | IncludeCollectibles = CollectiblesToggle.IsOn, |
| 59 | }; |
| 60 | _feed.Clear(); |
| 61 | Append(new FeedLine( |
| 62 | $" sweeping as {AppState.Current.Active.Username} (max {request.MaxPrice} R$)", |
| 63 | FeedTone.Accent)); |
| 64 | var jobId = AppState.Current.Engine.StartSweep(request); |
| 65 | _runner?.Start(jobId); |
| 66 | StartButton.IsEnabled = false; |
| 67 | StopButton.IsEnabled = true; |
| 68 | _metricsTimer?.Start(); |
| 69 | } |
| 70 | |
| 71 | private void OnStop(object sender, RoutedEventArgs e) |
| 72 | { |
| 73 | _runner?.Cancel(); |
| 74 | Append(new FeedLine(" stopping after in flight requests settle", FeedTone.Warn)); |
| 75 | } |
| 76 | |
| 77 | private void OnEvent(JobEvent item) |
| 78 | { |
| 79 | if (item.Type == "progress") |
| 80 | { |
| 81 | ScannedText.Text = item.Scanned.ToString("N0"); |
| 82 | MatchedText.Text = item.Matched.ToString("N0"); |
| 83 | return; |
| 84 | } |
| 85 | if (item.Type == "purchase" && item.Success) |
| 86 | { |
| 87 | PurchasedText.Text = (ulong.Parse(PurchasedText.Text.Replace(",", "")) + 1).ToString("N0"); |
| 88 | } |
| 89 | Append(FeedLine.From(item)); |
| 90 | } |
| 91 | |
| 92 | private void OnCompleted(string result) |
| 93 | { |
| 94 | StartButton.IsEnabled = true; |
| 95 | StopButton.IsEnabled = false; |
| 96 | _metricsTimer?.Stop(); |
| 97 | RefreshMetrics(); |
| 98 | Append(new FeedLine(" job finished", FeedTone.Good)); |
| 99 | } |
| 100 | |
| 101 | private void RefreshMetrics() |
| 102 | { |
| 103 | var metrics = AppState.Current.Engine.Metrics(); |
| 104 | RequestsText.Text = metrics.Requests.ToString("N0"); |
| 105 | LatencyText.Text = $"{metrics.AverageLatencyMs:F0} ms"; |
| 106 | } |
| 107 | |
| 108 | private void Append(FeedLine line) |
| 109 | { |
| 110 | if (_feed.Count >= MaxFeedLines) |
| 111 | { |
| 112 | _feed.RemoveAt(0); |
| 113 | } |
| 114 | _feed.Add(line); |
| 115 | if (_feed.Count > 0) |
| 116 | { |
| 117 | FeedList.ScrollIntoView(_feed[^1]); |
| 118 | } |
| 119 | } |
| 120 | } |