Changeset - 8ee5f618b07c
[Not reviewed]
Merge default
0 1 0
Tess Snider (Malkyne) - 3 years ago 2021-07-26 08:16:19
this@malkyne.org
Merge
1 file changed with 12 insertions and 11 deletions:
0 comments (0 inline, 0 general)
ViewModels/RecipeGeneratorViewModel.cs
Show inline comments
 
using System;
 
using System.Collections.Concurrent;
 
using System.Collections.Generic;
 
using System.Collections.ObjectModel;
 
using System.Diagnostics;
 
using System.Threading.Tasks;
 
using Avalonia.Threading;
 
using DesertPaintCodex.Models;
 
using DesertPaintCodex.Services;
 
using ReactiveUI;
 

	
 
namespace DesertPaintCodex.ViewModels
 
{
 
    public class RecipeGeneratorViewModel : ViewModelBase
 
    {
 
        private const long   UpdateInterval  = 2000;  // ms
 
        private const long   UpdateInterval  = 200;  // ms
 
        private const long   SaveInterval    = 30000; // ms
 
        private const string PaintStateFile  = "dp_generator_state";
 
        private const string RibbonStateFile = "dp_generator_ribbon_state";
 
        
 
        private int _selectedView = 0;
 
        public int SelectedView { get => _selectedView; private set => this.RaiseAndSetIfChanged(ref _selectedView, value); }
 
        
 
        private float _progress = 0;
 
        public float Progress { get => _progress; private set => this.RaiseAndSetIfChanged(ref _progress, value); }
 

	
 
        private int _permutationCount = 0;
 
        public int PermutationCount { get => _permutationCount; private set => this.RaiseAndSetIfChanged(ref _permutationCount, value); }
 
        private string _permutationCount = "0";
 
        public string PermutationCount { get => _permutationCount; private set => this.RaiseAndSetIfChanged(ref _permutationCount, value); }
 
        
 

	
 
        private DateTime _mostRecentTime;
 
        public DateTime MostRecentTime { get => _mostRecentTime; private set => this.RaiseAndSetIfChanged(ref _mostRecentTime, value); }
 

	
 
        private GeneratorRecipe? _mostRecentRecipe;
 
        public GeneratorRecipe? MostRecentRecipe { get => _mostRecentRecipe; private set => this.RaiseAndSetIfChanged(ref _mostRecentRecipe, value); }
 

	
 

	
 
        private int _productIndex = 0;
 
        public int ProductIndex { get => _productIndex; private set => this.RaiseAndSetIfChanged(ref _productIndex, value); }
 

	
 
        private int _maxReagents;
 
        public int MaxReagents { get => _maxReagents; private set => this.RaiseAndSetIfChanged(ref _maxReagents, value); }
 
        
 
        private int _maxConcentration;
 
        public int MaxConcentration { get => _maxConcentration; private set => this.RaiseAndSetIfChanged(ref _maxConcentration, value); }
 
        
 
        private int _fullQuantity;
 
        public int FullQuantity { get => _fullQuantity; private set => this.RaiseAndSetIfChanged(ref _fullQuantity, value); }
 
        
 
        private int _fullQuantityDepth;
 
        public int FullQuantityDepth { get => _fullQuantityDepth; private set => this.RaiseAndSetIfChanged(ref _fullQuantityDepth, value); }
 

	
 
        private int _maxConcentrationMax;
 
        public int MaxConcentrationMax { get => _maxConcentrationMax; private set => this.RaiseAndSetIfChanged(ref _maxConcentrationMax, value); }
 
        
 
        private int _fullQuantitynMax;
 
        public int FullQuantityMax { get => _fullQuantitynMax; private set => this.RaiseAndSetIfChanged(ref _fullQuantitynMax, value); }
 
        
 
        
 
        #region Flags
 

	
 
        private bool _isPaused = false;
 
        public bool IsPaused  { get => _isPaused; private set => this.RaiseAndSetIfChanged(ref _isPaused, value); }
 

	
 
        private bool _isInProgress = false;
 
        public bool IsInProgress { get => _isInProgress; private set => this.RaiseAndSetIfChanged(ref _isInProgress, value); }
 

	
 
        private bool _isRunning = false;
 
        public bool IsRunning {  get => _isRunning; private set => this.RaiseAndSetIfChanged(ref _isRunning, value); }
 

	
 
        private bool _canStart = false;
 
        public bool CanStart {  get => _canStart; private set => this.RaiseAndSetIfChanged(ref _canStart, value); }
 
        
 
        private bool _canClear = false;
 
        public bool CanClear {  get => _canClear; private set => this.RaiseAndSetIfChanged(ref _canClear, value); }
 

	
 
        #endregion // Flags
 
        
 
        #region Collections
 
        
 
        public ObservableCollection<Reagent> Reagents { get; } = new();
 
        public ObservableCollection<GeneratorRecipe> AllRecipes { get; } = new();
 
        
 
        #endregion // Collections
 
        
 
        private readonly RecipeGenerator _generator;
 

	
 
        private DateTime _lastSave = DateTime.Now;
 
        private readonly PlayerProfile _profile;
 
        private uint _minConcentration;
 

	
 
        private readonly ConcurrentQueue<PaintRecipe> _pendingNewRecipes = new();
 
        private volatile int _newRecipeCount;
 
        private volatile bool _updatesAvailable;
 
        private readonly DispatcherTimer _updateTimer;
 
        
 
        private bool _ribbonMode;
 
        private bool _unsavedRecipes;
 
        private bool _saving;
 

	
 
        
 
        public RecipeGeneratorViewModel()
 
        {
 
            List<string> reagentNames = ReagentService.Names;
 
            foreach (string name in reagentNames)
 
            {
 
                Reagents.Add(ReagentService.GetReagent(name));
 
            }
 
            
 
            SettingsService.Get("Generator.RibbonMode", out bool ribbonMode, false);
 
            ProductIndex = ribbonMode ? 1 : 0;
 
            _ribbonMode = ribbonMode;
 

	
 
            PlayerProfile? profile = ProfileManager.CurrentProfile;
 
            Debug.Assert(profile != null);
 
            _profile = profile;
 
            
 
            _profile.LoadRecipes();
 

	
 
            _generator = new RecipeGenerator();
 

	
 
            _generator.Progress  += OnProgress;
 
            _generator.NewRecipe += OnNewRecipe;
 
            _generator.Finished  += OnGeneratorStopped;
...
 
@@ -149,277 +149,278 @@ namespace DesertPaintCodex.ViewModels
 
        {
 
            IsInProgress = true;
 
            IsRunning = true;
 
            ReagentService.SaveProfileReagents(_profile.ReagentFile);
 
            SaveSettings(_ribbonMode ? "Ribbon" : "Paint");
 
            _updateTimer.Start();
 
            SelectedView = 1;
 
            
 
            _generator.BeginRecipeGeneration(
 
                _minConcentration, 
 
                (uint)MaxConcentration, 
 
                1, 
 
                (uint)MaxReagents, 
 
                (uint)FullQuantityDepth, 
 
                (uint)FullQuantity);
 
        }
 

	
 
        
 
        public void End()
 
        {
 
            IsPaused     = false;
 
            IsRunning    = false;
 
            IsInProgress = false;
 
            _updateTimer.Stop();
 
            _profile.SaveRecipes();
 
            
 
            CanClear = true;
 
        }
 

	
 
        public void Pause()
 
        {
 
            IsPaused  = true;
 
            IsRunning = false;
 
            _updateTimer.Stop();
 
            _generator.Stop();
 
        }
 

	
 
        public void Resume()
 
        {
 
            IsPaused  = false;
 
            IsRunning = true;
 
            _updateTimer.Start();
 
            SelectedView = 1;
 
            
 
            _generator.ResumeRecipeGeneration();
 

	
 
        }
 

	
 
        public async Task Clear()
 
        {
 
            bool result = await ShowYesNoBox("Clear Recipes", "Are you sure you want to clear your recipes?");
 
            if (result)
 
            {
 
                CanClear = false;
 
                if (_ribbonMode)
 
                {
 
                    _profile.ClearRibbonRecipes();
 
                }
 
                else
 
                {
 
                    _profile.ClearPaintRecipes();
 
                }
 

	
 
                foreach (GeneratorRecipe recipe in AllRecipes)
 
                {
 
                    recipe.ClearRecipe();
 
                }
 
                
 
                _generator.Reset();
 
            }
 
        }
 

	
 
        private void InitStateForPaint()
 
        {
 
            MaxConcentrationMax = 20;
 
            FullQuantityMax = 20;
 
            InitState("Paint", PaintRecipe.PaintRecipeMinConcentration, _profile.Recipes, PaintStateFile);
 

	
 
        }
 

	
 
        private void InitStateForRibbons()
 
        {
 
            MaxConcentrationMax = 100;
 
            FullQuantityMax = 100;
 
            InitState("Ribbon", PaintRecipe.RibbonRecipeMinConcentration, _profile.RibbonRecipes, RibbonStateFile);
 
        }
 

	
 
        private void InitState(string mode, uint minConcentration, Dictionary<string, PaintRecipe> recipes, string stateFileName)
 
        {
 
            _minConcentration = minConcentration;
 
            _generator.InitRecipes(recipes);
 
            
 
            string stateFile = System.IO.Path.Combine(_profile.Directory, stateFileName);
 
            if (System.IO.File.Exists(stateFile))
 
            {
 
                _generator.LoadState(stateFile);
 
                
 

	
 
                // Always set these, or the values will be invalid
 
                MaxReagents = (int)_generator.MaxReagents;
 
                MaxConcentration = (int)_generator.MaxConcentration;
 
                FullQuantity = (int)_generator.FullQuantity;
 
                FullQuantityDepth = (int)_generator.FullQuantityDepth;
 

	
 
                if (_generator.CanResume)
 
                {
 
                    IsInProgress = true;
 
                    IsPaused = true;
 

	
 
                    MaxReagents       = (int) _generator.MaxReagents;
 
                    MaxConcentration  = (int) _generator.MaxConcentration;
 
                    FullQuantity      = (int) _generator.FullQuantity;
 
                    FullQuantityDepth = (int) _generator.FullQuantityDepth;
 
                    
 
                    SaveSettings(mode);
 
                }
 
                else
 
                {
 
                    IsInProgress = false;
 
                    IsPaused = false;
 
                    CanClear = true;
 
                }
 
            }
 
            else if (mode == "Paint")
 
            {
 
                LoadPaintSettings();
 
            }
 
            else
 
            {
 
                LoadRibbonSettings();
 
            }
 

	
 
            AllRecipes.Clear();
 

	
 
            foreach (PaintColor color in PaletteService.Colors)
 
            {
 
                GeneratorRecipe genRecipe = new(color);
 
                if (recipes.TryGetValue(color.Name, out PaintRecipe? recipe)) //  && recipe.IsValidForConcentration(minConcentration)) // This check is redundant, since they're checked when loading.
 
                {
 
                    genRecipe.DraftRecipe(recipe);
 
                }
 
                AllRecipes.Add(genRecipe);
 
            }
 
        }
 

	
 
        #region Event Handlers
 
        private void OnProgress(object? sender, EventArgs args)
 
        {
 
            _newRecipeCount = (int)_generator.RecipeCount;
 
            _updatesAvailable = true;
 
        }
 

	
 
        private void OnNewRecipe(object? sender, NewRecipeEventArgs args)
 
        {
 
            PaintRecipe recipe = new(args.Recipe);
 
            _pendingNewRecipes.Enqueue(recipe);
 
            _updatesAvailable = true;
 
        }
 
        
 
        
 
        private void OnGeneratorStopped(object? sender, EventArgs args)
 
        {
 
            SaveState();
 
            
 
            if (_saving)
 
            {
 
                _generator.ResumeRecipeGeneration();
 
                _lastSave = DateTime.Now;
 
                _saving = false;
 
                return;
 
            }
 

	
 
            if (IsPaused) return;
 

	
 
            End();
 
        }
 

	
 
        private void Update(object? sender, EventArgs e)
 
        {
 
            if (!_updatesAvailable) return;
 
            if (_saving) return;
 

	
 
            _updatesAvailable = false;
 

	
 
            DateTime now = DateTime.Now;
 
            
 

 
            // Update test count.
 
            PermutationCount = _newRecipeCount;
 
            PermutationCount = $"{_newRecipeCount:n0}";
 
            
 
            // Pull in new recipes.
 
            if (!_pendingNewRecipes.IsEmpty)
 
            {
 
                GeneratorRecipe? lastRecipe = null;
 
                while (_pendingNewRecipes.TryDequeue(out PaintRecipe? newRecipe))
 
                {
 
                    string recipeColor = PaletteService.FindNearest(newRecipe.ReactedColor);
 
                    foreach (GeneratorRecipe recipe in AllRecipes)
 
                    {
 
                        if (recipe.Color.Name != recipeColor) continue;
 
                        recipe.DraftRecipe(newRecipe);
 
                        lastRecipe = recipe;
 
                        if (_ribbonMode)
 
                        {
 
                            _profile.SetRibbonRecipe(recipeColor, newRecipe);
 
                        }
 
                        else
 
                        {
 
                            _profile.SetRecipe(recipeColor, newRecipe);
 
                        }
 
                        break;
 
                    }
 
                }
 

	
 
                // If at least one recipe was processed, let's check to see if it's time to save, and
 
                // highlight that recipe.
 
                if (lastRecipe != null)
 
                {
 
                    _unsavedRecipes = true;
 
                    MostRecentRecipe = lastRecipe;
 
                    MostRecentTime = now;
 
                }
 
            }
 
            
 
            // Save if it is time.
 
            if (!((now - _lastSave).TotalMilliseconds > SaveInterval)) return;
 
            
 
            if (_unsavedRecipes)
 
            {
 
                _profile.SaveRecipes();
 
                _unsavedRecipes = false;
 
            }
 

	
 
            _saving = true;
 
            _generator.Stop();
 
        }
 
        
 
        #endregion
 

	
 

	
 
        private void SavePaintSettings()
 
        {
 
            SaveSettings("Paint");
 
        }
 

	
 
        private void LoadPaintSettings()
 
        {
 
            LoadSettings("Paint", 5, 20, 20, 4);
 
        }
 

	
 
        private void SaveRibbonSettings()
 
        {
 
            SaveSettings("Ribbon");
 
        }
 

	
 
        private void LoadRibbonSettings()
 
        {
 
            LoadSettings("Ribbon", 5, 75, 75, 4);
 
        }
 

	
 

	
 
        private void SaveSettings(string mode)
 
        {
 
            SettingsService.Set($"Generator.{mode}.MaxReagents",       MaxReagents);
 
            SettingsService.Set($"Generator.{mode}.MaxConcentration",  MaxConcentration);
 
            SettingsService.Set($"Generator.{mode}.FullQuantity",      FullQuantity);
 
            SettingsService.Set($"Generator.{mode}.FulLQuantityDepth", FullQuantityDepth);
 
        }
 

	
 
        private void LoadSettings(string mode, int defaultMaxReagents, int defaultMaxConcentration,
 
            int defaultFullQuantity, int defaultFullQuantityDepth)
 
        {
 
            SettingsService.Get($"Generator.{mode}.MaxReagents",       out int  maxReagents,       defaultMaxReagents);
 
            SettingsService.Get($"Generator.{mode}.MaxConcentration",  out int  maxConcentration,  defaultMaxConcentration);
 
            SettingsService.Get($"Generator.{mode}.FullQuantity",      out int  fullQuantity,      defaultFullQuantity);
 
            SettingsService.Get($"Generator.{mode}.FullQuantityDepth", out int  fullQuantityDepth, defaultFullQuantityDepth);
 
            
 
            MaxReagents       = maxReagents;
 
            MaxConcentration  = maxConcentration;
 
            FullQuantity      = fullQuantity;
 
            FullQuantityDepth = fullQuantityDepth;
 
        }
 

	
 
        private void SaveState()
 
        {
0 comments (0 inline, 0 general)