mercredi 2 décembre 2015

Processing huge search in c# wpf using SQLite

I am building a optimizer for a game in WPF C#. You start of by adding all the monsters you have and the runes (runes are almost like armor/weapons). You can then choose one of the monsters from a list that you want to optimize. Each monster can equip 6 runes (each rune has a specific slot it can go to, 1-6). The user can then choose what type of runeset it wants (up to 3 diffrent). You need to have 4 or 2 pices (depending on runeset) of the same rune for it be be a runeset. The user can also specify what main type of rune it wants on slot 2,4 and 6 e.x atk% or hp%. After this the user hits a button to optimize.

Here comes my problem, since we want to add all the results in a datagrid so that the user can sort after most possible hp, speed and atk etc. This list will be huge depening on what user has specified in the comboboxes (filtering runeset, type of rune). If no filter is given we are talking about 30^6 results, maybe even more. Ideally the user should be able to do such a big search since it gives the best result, but if that is not possible i can set a limit so the user have to choose some of the filters to cut down the results. My first approach on this was just to make a list add each possible runecombination, and that resultet in out of memory after a few milion searches. Then i tried to save the data to an xml file, this worked but was very slow (did never finish a full search becouse it would take weeks). Then i check up SQLite, and this is where i am now, its decent fast and it might just work. But I am fresh with C# so my code might be very bad and poorly optimized so could need some guidence here to help me out on the code. I dont want to post my full code since its long so I will paste a few important ones and maybe someone can give me some tips on how to optimize this better:

This is the code for the Rune class:

public class Rune
{
    public int Rune_ID { get; set; }
    public int Rune_Monster { get; set; }
    public string Rune_Monster_Name { get; set; }
    public string Rune_Set { get; set; }
    public int Rune_Slot { get; set; }
    public int Rune_Grade { get; set; }
    public int Rune_Level { get; set; }
    public string Rune_Main_Type { get; set; }
    public int Rune_Main_Amount { get; set; }
    public string Rune_Innate_Type { get; set; }
    public int Rune_Innate_Amount { get; set; }
    public string Rune_Sub1_Type { get; set; }
    public int Rune_Sub1_Amount { get; set; }
    public string Rune_Sub2_Type { get; set; }
    public int Rune_Sub2_Amount { get; set; }
    public string Rune_Sub3_Type { get; set; }
    public int Rune_Sub3_Amount { get; set; }
    public string Rune_Sub4_Type { get; set; }
    public int Rune_Sub4_Amount { get; set; }
    public int Rune_Locked { get; set; }
    public int Rune_Sub_HP_F { get; set; }
    public int Rune_Sub_HP_P { get; set; }
    public int Rune_Sub_Atk_F { get; set; }
    public int Rune_Sub_Atk_P { get; set; }
    public int Rune_Sub_Def_F { get; set; }
    public int Rune_Sub_Def_P { get; set; }
    public int Rune_Sub_Spd { get; set; }
    public int Rune_Sub_CritRate { get; set; }
    public int Rune_Sub_CritDmg { get; set; }
    public int Rune_Sub_Res { get; set; }
    public int Rune_Sub_Acc { get; set; }
}

Optimize button click:

    private void butOptimize_Click(object sender, RoutedEventArgs e)
    {
        //OptimizeList.Clear();

        if (cmbOptMonsters.SelectedItem != null)
        {
            selectedRuneSets.Clear();

            //Makes list of selected runesets
            if (cmbOptSet1.SelectedIndex != -1)
                selectedRuneSets.Add(cmbOptSet1.SelectedValue.ToString());
            if (cmbOptSet2.SelectedIndex != -1)
                selectedRuneSets.Add(cmbOptSet2.SelectedValue.ToString());
            if (cmbOptSet3.SelectedIndex != -1)
                selectedRuneSets.Add(cmbOptSet3.SelectedValue.ToString());

            //Makes list of selected runeslots
            if (cmbOptSlot2.SelectedIndex != -1)
                selectedRuneSlot2 = cmbOptSlot2.SelectedValue.ToString();
            if (cmbOptSlot4.SelectedIndex != -1)
                selectedRuneSlot4 = cmbOptSlot4.SelectedValue.ToString();
            if (cmbOptSlot6.SelectedIndex != -1)
                selectedRuneSlot6 = cmbOptSlot6.SelectedValue.ToString();

            var selMob = from y in Monsters where y.Monster_Name == cmbOptMonsters.SelectedValue.ToString() select y;
            ref2Monster = selMob.First();

            worker.RunWorkerAsync();
        }
    }

Background worker do work code:

     // run all background tasks here
    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {

        BackgroundWorker worker = (BackgroundWorker)sender;
        List<string> listCurrentSets = new List<string>();

        IEnumerable<Rune> Slot_1 = from y in Runes where y.Rune_Slot == 1 select y;
        IEnumerable<Rune> Slot_2 = from y in Runes where y.Rune_Slot == 2 select y;
        IEnumerable<Rune> Slot_3 = from y in Runes where y.Rune_Slot == 3 select y;
        IEnumerable<Rune> Slot_4 = from y in Runes where y.Rune_Slot == 4 select y;
        IEnumerable<Rune> Slot_5 = from y in Runes where y.Rune_Slot == 5 select y;
        IEnumerable<Rune> Slot_6 = from y in Runes where y.Rune_Slot == 6 select y;
        IEnumerable<Rune> tempStoreRunes = from y in Runes where y.Rune_Monster_Name == ref2Monster.Monster_Name select y;

        int rcount1 = Runes.Count(n => n.Rune_Slot == 1);
        int rcount2 = Runes.Count(n => n.Rune_Slot == 2);
        int rcount3 = Runes.Count(n => n.Rune_Slot == 3);
        int rcount4 = Runes.Count(n => n.Rune_Slot == 4);
        int rcount5 = Runes.Count(n => n.Rune_Slot == 5);
        int rcount6 = Runes.Count(n => n.Rune_Slot == 6);
        int modulo = 1000;
        double count;
        double countLoops = 0;
        double countMatch = 0;
        double timeETA = 0.0;
        bool setIsMatch;

        Optimized optTempSearch;
        var stopwatch = new System.Diagnostics.Stopwatch();
        var time = TimeSpan.FromMilliseconds(timeETA);

        if (selectedRuneSlot2 != "")
        {
            Slot_2 = from y in Runes where y.Rune_Slot == 2 && y.Rune_Main_Type == selectedRuneSlot2 select y;
            rcount2 = Runes.Count(n => n.Rune_Slot == 2 && n.Rune_Main_Type == selectedRuneSlot2);
        }
        if (selectedRuneSlot4 != "")
        {
            Slot_4 = from y in Runes where y.Rune_Slot == 4 && y.Rune_Main_Type == selectedRuneSlot4 select y;
            rcount4 = Runes.Count(n => n.Rune_Slot == 4 && n.Rune_Main_Type == selectedRuneSlot4);
        }
        if (selectedRuneSlot6 != "")
        {
            Slot_6 = from y in Runes where y.Rune_Slot == 6 && y.Rune_Main_Type == selectedRuneSlot6 select y;
            rcount6 = Runes.Count(n => n.Rune_Slot == 6 && n.Rune_Main_Type == selectedRuneSlot6);
        }

        count = rcount1 * rcount2 * rcount3 * rcount4 * rcount5 * rcount6;

        OptSearchCount = count;

        //Starts timer
        stopwatch.Start();

        // Creates new sqlite database if it is not found
        using (var conn = new SQLiteConnection(
            @"Data Source=" + AppDomain.CurrentDomain.BaseDirectory + "\\Resources\\Optimizer.sqlite"))
        {
            //Connects to database
            conn.Open();

            using (var cmd = new SQLiteCommand(conn))
            {
                using (var transaction = conn.BeginTransaction())
                {
                    foreach (var r1 in Slot_1)
                    {
                        foreach (var r2 in Slot_2)
                        {
                            foreach (var r3 in Slot_3)
                            {
                                foreach (var r4 in Slot_4)
                                {
                                    foreach (var r5 in Slot_5)
                                    {
                                        foreach (var r6 in Slot_6)
                                        {
                                            countLoops++;

                                            //Makes a list of the current sets that is made by runecombination
                                            listCurrentSets = CheckIfRuneSet(r1, r2, r3, r4, r5, r6);

                                            //Check if selectedRuneSets exists in the list listCurrentSets.
                                            //We only want to make a search on the selected runes
                                            setIsMatch = new HashSet<string>(listCurrentSets).IsSupersetOf(selectedRuneSets);

                                            if (setIsMatch == true)
                                            {
                                                countMatch++;

                                                //Gets the current stat of the monster using the current runes from loop                 
                                                optTempSearch = SumStat(ref2Monster, r1, r2, r3, r4, r5, r6, countLoops);

                                                //Makes a command that will be sent to data base later in commit
                                                cmd.CommandText = "INSERT INTO OptSearch (id, runeids, atk, def, hp, crate, cdmg, acc, spd, res) VALUES (" + countLoops + ", '" + optTempSearch.Opt_RuneIDs + "', " + optTempSearch.Opt_ATK + ", " + optTempSearch.Opt_DEF + ", " + optTempSearch.Opt_HP + ", " + optTempSearch.Opt_CRate + ", " + optTempSearch.Opt_CDmg + ", " + optTempSearch.Opt_Acc + ", " + optTempSearch.Opt_Spd + ", " + optTempSearch.Opt_Res + ");";

                                                //Executes database command
                                                cmd.ExecuteNonQuery();
                                            }

                                            //Uses modulus to not opdate the progress to fast, not nessecary. 
                                            //Then reports the progress to the worker
                                            if (countLoops % modulo == 0)
                                            {
                                                worker.ReportProgress((int)countLoops);

                                                //Calculates estimated time
                                                timeETA = stopwatch.Elapsed.TotalMilliseconds / countLoops;
                                                time = TimeSpan.FromMilliseconds(timeETA * count);

                                                //You cannot change the ui since its owned by the "main", so to by pass we do this
                                                this.Dispatcher.BeginInvoke((Action)delegate ()
                                                {
                                                    lblOptMatch.Content = "Matches: " + (int)countMatch;
                                                    lblOptETA.Content = time.ToString(@"dd\:hh\:mm\:ss");
                                                });
                                            }

                                        }
                                    }
                                }
                            }
                        }
                    }

                    //Saves to database
                    transaction.Commit();
                }
            }

            //Closes database connection
            conn.Close();
        }

        UpdateMonsterStats(ref2Monster, tempStoreRunes);
        //xmlDocument.Save(AppDomain.CurrentDomain.BaseDirectory + "\\Resources\\Optimizer.xml");
    }

If anyone need the full code just ask and i can upload the project. Here is a picture of search gui: http://ift.tt/1lXoR6Z

Aucun commentaire:

Enregistrer un commentaire