Changeset - 7b2e8ac4935e
[Not reviewed]
default
0 5 0
Jason Maltzen (jmaltzen) - 9 years ago 2015-12-25 03:17:40
jason.maltzen@unsanctioned.net
Save recipe generator state to allow resume across app runs. Periodically save current generated recipe list, instead of just saving on stop/pause.
5 files changed with 460 insertions and 82 deletions:
0 comments (0 inline, 0 general)
.hgignore
Show inline comments
 
syntax: glob
 
obj/
 
*.userprefs
 
*.orig
 
*.swp
 
*.zip
 
.DS_Store
 
mac/build/DesertPaintLab.app
 
mac/bin
RecipeGenerator.cs
Show inline comments
...
 
@@ -16,18 +16,20 @@
 
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 
 THE SOFTWARE.
 
*/
 

	
 
using System;
 
using System.IO;
 
using System.Collections.Generic;
 
using System.Collections.Concurrent;
 
using System.Text.RegularExpressions;
 
using System.Threading;
 

	
 
namespace DesertPaintLab
 
{
 
    public class NewRecipeEventArgs : EventArgs
 
    {
 
        string color;
 
        PaintRecipe recipe;
...
 
@@ -50,52 +52,62 @@ namespace DesertPaintLab
 
            get {
 
                return recipe;
 
            }
 
        }
 
    }
 

	
 
    public class RecipeGenerator
 
    {
 
        private class SearchNode
 
        protected class SearchNode
 
        {
 
            //int initialReagentCount;
 
            List<uint> reagents;
 
            public List<uint> Reagents
 
            {
 
                get
 
                {
 
                    return reagents;
 
                }
 
            }
 

	
 
            HashSet<uint> reagentInUse = new HashSet<uint>();
 
            List<Reagent> costSortedReagents;
 
            PaintRecipe testRecipe = null;
 
            public PaintRecipe TestRecipe
 
            {
 
                get
 
                {
 
                    return testRecipe;
 
                }
 
                set
 
                {
 
                    testRecipe = value;
 
                }
 
            }
 

	
 
            public int InitialCount { get; private set; }
 
            public uint CurrentTargetQuantity { get; set; }
 
            public uint MaxQuantity { get; set; }
 
            public uint UsedQuantity { get; private set; }
 
            public uint CatalystCount { get; set; }
 

	
 
            uint maxReagents;
 
            public uint MaxReagents
 
            {
 
                get
 
                {
 
                    return maxReagents;
 
                }
 
                set
 
                {
 
                    maxReagents = value;
 
                    currentWeights = new uint[maxReagents];
 
                }
 
            }
 
            public uint UsedQuantity { get; private set; }
 

	
 
            public uint CatalystCount { get; set; }
 

	
 
            uint[] currentWeights;
 
            public uint[] CurrentWeights
 
            {
 
                get
 
                {
 
                    return currentWeights;
 
                }
...
 
@@ -120,23 +132,24 @@ namespace DesertPaintLab
 
                this.costSortedReagents = new List<Reagent>(costSortedReagents);
 
                this.reagents = new List<uint>();
 
                this.reagents.Add(NextFreeReagent(startReagent));
 
                InitialCount = 1; // don't iterate up beyond the start reagent
 
                MaxReagents = 1;
 
                UsedQuantity = 0;
 
            }
 

	
 
            public int InitialCount { get; private set; }
 
            public List<uint> Reagents
 
            public SearchNode(List<Reagent> costSortedReagents)
 
            {
 
                get
 
                {
 
                    return reagents;
 
                }
 
                this.costSortedReagents = costSortedReagents;
 
                this.reagents = new List<uint>();
 
                this.reagents.Add(NextFreeReagent(0));
 
                InitialCount = 0;
 
                MaxReagents = 1;
 
                UsedQuantity = 0;
 
            }
 

	
 
            public Reagent Reagent(int idx)
 
            {
 
                return costSortedReagents[(int)reagents[idx]];
 
            }
 

	
 
            public uint LastReagent
...
 
@@ -243,28 +256,155 @@ namespace DesertPaintLab
 
            }
 

	
 
            public void SetWeight(int idx, uint quantity)
 
            {
 
                UsedQuantity -= currentWeights[idx];
 
                currentWeights[idx] = quantity;
 
                UsedQuantity += quantity;
 
            }
 

	
 
            public void SaveState(StreamWriter writer)
 
            {
 
                writer.WriteLine("---SearchNode---");
 
                writer.WriteLine("MaxReagents: {0}", MaxReagents);
 
                writer.WriteLine("Reagents: {0}", reagents.Count);
 
                for (int i = 0; i < reagents.Count; ++i)
 
                {
 
                    uint idx = reagents[i];
 
                    uint weight = currentWeights[i];
 
                    writer.WriteLine("Reagent: {0},{1},{2}", idx, reagentInUse.Contains(idx) ? 1 : 0, weight);
 
                }
 
                // pulled from parent: List<Reagent> costSortedReagents;
 
                // new on construct: PaintRecipe testRecipe = null;
 
                writer.WriteLine("CurrentTargetQuantity: {0}", CurrentTargetQuantity);
 
                writer.WriteLine("MaxQuantity: {0}", MaxQuantity);
 
                writer.WriteLine("UsedQuantity: {0}", UsedQuantity);
 
                writer.WriteLine("CatalystCount: {0}", CatalystCount);
 
                writer.WriteLine("InitialCount: {0}", InitialCount);
 
                writer.WriteLine("---EndNode---");
 
                
 
            }
 

	
 
            static Regex keyValueRegex = new Regex(@"(\w+)\:\s*(.*)\s*$");
 
            static Regex reagentPartsRegex = new Regex(@"(?<id>\d+),(?<inUse>\d+),(?<weight>\d+)");
 
            public bool LoadState(StreamReader reader)
 
            {
 
                string line = reader.ReadLine();
 
                if (!line.Equals("---SearchNode---"))
 
                {
 
                    return false;
 
                }
 

	
 
                bool success = true;
 
                Match match;
 
                int reagentIdx = 0;
 
                while ((line = reader.ReadLine()) != null)
 
                {
 
                    if (line.Equals("---EndNode---"))
 
                    {
 
                        break;
 
                    }
 
                    match = keyValueRegex.Match(line);
 
                    if (match.Success)
 
                    {
 
                        switch (match.Groups[1].Value)
 
                        {
 
                            case "Reagents":
 
                                {
 
                                    int reagentCount = int.Parse(match.Groups[2].Value);
 
                                    reagents = new List<uint>(reagentCount);
 
                                    reagentInUse.Clear();
 
                                    reagentIdx = 0;
 
                                }
 
                                break;
 
                            case "Reagent":
 
                                {
 
                                    Match reagentInfo = reagentPartsRegex.Match(match.Groups[2].Value);
 
                                    if (reagentInfo.Success)
 
                                    {
 
                                        uint reagentId = uint.Parse(reagentInfo.Groups["id"].Value);
 
                                        int isInUse = int.Parse(reagentInfo.Groups["inUse"].Value);
 
                                        uint weight = uint.Parse(reagentInfo.Groups["weight"].Value);
 
                                        reagents.Add(reagentId);
 
                                        currentWeights[reagentIdx] = weight;
 
                                        if (isInUse != 0)
 
                                        {
 
                                            reagentInUse.Add(reagentId);
 
                                        }
 
                                    }
 
                                    else
 
                                    {
 
                                        success = false;
 
                                    }
 
                                }
 
                                break;
 
                            case "CurrentTargetQuantity":
 
                                {
 
                                    uint value = uint.Parse(match.Groups[2].Value);
 
                                    CurrentTargetQuantity = value;
 
                                }
 
                                break;
 
                            case "MaxQuantity":
 
                                {
 
                                    uint value = uint.Parse(match.Groups[2].Value);
 
                                    MaxQuantity = value;
 
                                }
 
                                break;
 
                            case "MaxReagents":
 
                                {
 
                                    uint value = uint.Parse(match.Groups[2].Value);
 
                                    MaxReagents = value;
 
                                }
 
                                break;
 
                            case "UsedQuantity":
 
                                {
 
                                    uint value = uint.Parse(match.Groups[2].Value);
 
                                    UsedQuantity = value;
 
                                }
 
                                break;
 
                            case "CatalystCount":
 
                                {
 
                                    uint value = uint.Parse(match.Groups[2].Value);
 
                                    CatalystCount = value;
 
                                }
 
                                break;
 
                            case "InitialCount":
 
                                {
 
                                    int value = int.Parse(match.Groups[2].Value);
 
                                    InitialCount = value;
 
                                }
 
                                break;
 
                            default:
 
                                success = false;
 
                                break;
 
                        }
 
                    }
 
                    else
 
                    {
 
                        success = false;
 
                        break;
 
                    }
 
                }
 
                return success;
 
            }
 
        }
 

	
 
        const uint DEFAULT_MAX_QUANTITY = 14; // minimum recipe: 10 base + 4 catalysts
 
        const uint DEFAULT_MAX_REAGENTS = 5;
 

	
 
        //uint maxQuantity; // maximum number of total ingredients
 
        uint maxReagents; // maximum number of reagents to use in the recipe
 
        uint fullQuantityDepth; // at or equal this number of reagents, ignore ingredient settings for max quantity
 
        uint fullQuantity;  // The max number of a reagent to use at full quantity
 

	
 
        ReactionSet reactions;
 
        bool running = false;
 

	
 
        int runningThreads = 0;
 
        
 
        SortedDictionary<string, uint> recipeCosts = new SortedDictionary<string, uint>();
 
        SortedDictionary<string, PaintRecipe> recipes = new SortedDictionary<string, PaintRecipe>();
 

	
 
        uint totalReagents;
 

	
 
        List<Reagent> costSortedReagents = new List<Reagent>();
 

	
...
 
@@ -277,18 +417,19 @@ namespace DesertPaintLab
 

	
 
        bool requestCancel = false;
 

	
 
        // events
 
        public event EventHandler Finished;
 
        public event EventHandler Progress;
 
        public event EventHandler<NewRecipeEventArgs> NewRecipe;
 

	
 
        public RecipeGenerator()
 
        public RecipeGenerator(ReactionSet reactions)
 
        {
 
            this.reactions = reactions;
 
        }
 

	
 
        public SortedDictionary<string, PaintRecipe> Recipes
 
        {
 
            get
 
            {
 
                return recipes;
 
            }
...
 
@@ -297,64 +438,74 @@ namespace DesertPaintLab
 
        public int RecipeCount
 
        {
 
            get
 
            {
 
                return recipeCount;
 
            }
 
        }
 

	
 
        public bool CanResume
 
        {
 
            get
 
            {
 
                return (!running && (searchQueue.Count > 0));
 
            }
 
        }
 

	
 
        private class ReagentCostSort : IComparer<Reagent>
 
        {
 
            public int Compare(Reagent reagent1, Reagent reagent2)
 
            {
 
                return (int)reagent1.Cost - (int)reagent2.Cost;
 
            }
 
        }
 

	
 
        public void InitRecipes(SortedDictionary<string, PaintRecipe> recipes, ReactionSet reactions)
 
        public void InitRecipes(SortedDictionary<string, PaintRecipe> recipes)
 
        {
 
            if (running)
 
            {
 
                return;
 
            }
 
            this.reactions = reactions;
 
            foreach (PaintRecipe recipe in recipes.Values)
 
            {
 
                // TODO: copy?
 
                AddCheapestRecipe(recipe);
 
                PaintRecipe recipeCopy = new PaintRecipe(recipe);
 
                AddCheapestRecipe(recipeCopy);
 
            }
 
        }
 

	
 
        public void BeginRecipeGeneration(ReactionSet reactions, uint maxQuantity, uint maxReagents, uint fullQuantityDepth, uint fullQuantity)
 
        private void InitSortedReagents()
 
        {
 
            if (running)
 
            {
 
                // Already running - don't start again
 
                return;
 
            }
 
            this.running = true;
 

	
 
            this.reactions = reactions;
 
            //this.maxQuantity = maxQuantity;
 
            this.maxReagents = maxReagents;
 
            this.fullQuantity = fullQuantity;
 
            this.fullQuantityDepth = fullQuantityDepth;
 

	
 
            // first, sort reagents by cost.
 
            costSortedReagents.Clear();
 
            foreach (string name in ReagentManager.Names)
 
            {
 
                Reagent reagent = ReagentManager.GetReagent(name);
 
                if (reagent.Enabled)
 
                {
 
                    costSortedReagents.Add(reagent);
 
                }
 
            }
 
            costSortedReagents.Sort(new ReagentCostSort());
 
        }
 

	
 
        public void BeginRecipeGeneration(uint maxQuantity, uint maxReagents, uint fullQuantityDepth, uint fullQuantity)
 
        {
 
            if (running)
 
            {
 
                // Already running - don't start again
 
                return;
 
            }
 

	
 
            //this.maxQuantity = maxQuantity;
 
            this.maxReagents = maxReagents;
 
            this.fullQuantity = fullQuantity;
 
            this.fullQuantityDepth = fullQuantityDepth;
 

	
 
            // first, sort reagents by cost.
 
            InitSortedReagents();
 
            this.maxReagents = (uint)Math.Min(costSortedReagents.Count, this.maxReagents);
 

	
 
            totalReagents = (uint)costSortedReagents.Count;
 

	
 
            // Pre-populate recipes list with:
 
            // 1) 1-ingredient recipes @ 10db for all enabled ingredients with a count >= 10
 
            // 2) any previously-generated recipes
 
            foreach (Reagent reagent in costSortedReagents)
...
 
@@ -363,82 +514,224 @@ namespace DesertPaintLab
 
                {
 
                    PaintRecipe recipe = new PaintRecipe();
 
                    recipe.Reactions = reactions;
 
                    recipe.AddReagent(reagent.Name, 10);
 
                    AddCheapestRecipe(recipe);
 
                }
 
            }
 

	
 
            while (!searchQueue.IsEmpty)
 
            {
 
                SearchNode node;
 
                searchQueue.TryDequeue(out node);
 
            }
 
            for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx)
 
            {
 
                SearchNode initialNode = new SearchNode(costSortedReagents, reagentIdx);
 
                initialNode.MaxQuantity = maxQuantity;
 
                initialNode.MaxReagents = maxReagents;
 
                searchQueue.Enqueue(initialNode);
 
            }
 
            
 
            // start worker threads to do the actual work
 

	
 
            requestCancel = false;
 
            running = true;
 
            // Start the workers thread
 
            for (int i = 0; i < costSortedReagents.Count; ++i)
 
            {
 
                Thread thr = new Thread(new ThreadStart(this.Generate));
 
                generatorThreads.Add(thr);
 
            }
 
            foreach (Thread thr in generatorThreads)
 
            {
 
                thr.Start();
 
            }
 
        
 
            ResumeRecipeGeneration();
 
        }
 

	
 
        public void ResumeRecipeGeneration()
 
        {
 
            if (running)
 
            {
 
                // Already running - don't start again
 
                return;
 
            }
 
            this.running = true;
 
            running = true;
 
            requestCancel = false;
 

	
 
            for (int i = 0; i < costSortedReagents.Count; ++i)
 
            //System.Console.WriteLine("Resuming recipe generation: pre-threads={0} reagent count={1} search queue={2}", runningThreads, costSortedReagents.Count, searchQueue.Count);
 
            runningThreads = 0; // presumably!
 

	
 
            int threadCount = Math.Min(costSortedReagents.Count, searchQueue.Count);
 
            if (threadCount == 0)
 
            {
 
                if (Finished != null)
 
                {
 
                    Finished(this, null);
 
                }
 
            }
 
            generatorThreads.Clear();
 
            for (int i = 0; i < threadCount; ++i)
 
            {
 
                Thread thr = new Thread(new ThreadStart(this.Generate));
 
                generatorThreads.Add(thr);
 
            }
 
            foreach (Thread thr in generatorThreads)
 
            {
 
                thr.Start();
 
            }
 
        }
 

	
 
        public bool SaveState(string file)
 
        {
 
            if (running)
 
            {
 
                // can't save state while running
 
                return false;
 
            }
 

	
 
            using (StreamWriter writer = new StreamWriter(file, false))
 
            {
 
                writer.WriteLine("MaxReagents: {0}", maxReagents);
 
                writer.WriteLine("FullQuantityDepth: {0}", fullQuantityDepth);
 
                writer.WriteLine("FullQuantity: {0}", fullQuantity);
 
                writer.WriteLine("TotalReagents: {0}", totalReagents);
 
                writer.WriteLine("RecipeCount: {0}", recipeCount);
 
                foreach (KeyValuePair<string, PaintRecipe> pair in recipes)
 
                {
 
                    PaintRecipe recipe = pair.Value;
 
                    string colorName = Palette.FindNearest(recipe.ReactedColor);
 
                    writer.WriteLine("BeginRecipe: {0}", colorName);
 
                    foreach (PaintRecipe.RecipeIngredient ingredient in recipe.Ingredients)
 
                    {
 
                        writer.WriteLine("Ingredient: {0}={1}", ingredient.name, ingredient.quantity);
 
                    }
 
                    writer.WriteLine("EndRecipe: {0}", colorName);
 
                }
 
                writer.WriteLine("SearchNodes: {0}", searchQueue.Count);
 
                foreach (SearchNode node in searchQueue)
 
                {
 
                    node.SaveState(writer);
 
               }
 
            }
 
            return true;
 
        }
 

	
 
        static Regex keyValueRegex = new Regex(@"(?<key>\w+)\:\s*(?<value>.+)\s*");
 
        static Regex ingredientRegex = new Regex(@"(?<ingredient>(\w+\s)*\w+)\s*=\s*(?<quantity>\d)\s*");
 
        public bool LoadState(string file)
 
        {
 
            // cannot be running, and reactions must be set
 
            if (running)
 
            {
 
                return false;
 
            }
 

	
 
            InitSortedReagents();
 
            bool success = true;
 

	
 
            PaintRecipe currentRecipe = null;
 
            Match match;
 
            string line;
 
            using (StreamReader reader = new StreamReader(file, false))
 
            {
 
                while (success && ((line = reader.ReadLine()) != null))
 
                {
 
                    match = keyValueRegex.Match(line);
 
                    if (match.Success)
 
                    {
 
                        string value = match.Groups["value"].Value;
 
                        switch(match.Groups["key"].Value)
 
                        {
 
                            case "MaxReagents":
 
                                maxReagents = uint.Parse(value);
 
                                break;
 
                            case "FullQuantityDepth":
 
                                fullQuantityDepth = uint.Parse(value);
 
                                break;
 
                            case "FullQuantity":
 
                                fullQuantity = uint.Parse(value);
 
                                break;
 
                            case "TotalReagents":
 
                                totalReagents = uint.Parse(value);
 
                                break;
 
                            case "RecipeCount":
 
                                recipeCount = int.Parse(value);
 
                                break;
 
                            case "BeginRecipe":
 
                                currentRecipe = new PaintRecipe();
 
                                currentRecipe.Reactions = reactions;
 
                                break;
 
                            case "EndRecipe":
 
                                if (currentRecipe != null)
 
                                {
 
                                    PaintColor color = currentRecipe.ReactedColor;
 
                                    uint cost = currentRecipe.Cost;
 
                                    recipes[color.Name] = currentRecipe; // replace
 
                                    recipeCosts[color.Name] = cost;
 
                                    currentRecipe = null;
 
                                }
 
                                break;
 
                            case "Ingredient":
 
                                if (currentRecipe != null)
 
                                {
 
                                    Match ingredientMatch = ingredientRegex.Match(match.Groups["value"].Value);
 
                                    if (ingredientMatch.Success)
 
                                    {
 
                                        uint quantity = uint.Parse(ingredientMatch.Groups["quantity"].Value);
 
                                        currentRecipe.AddReagent(ingredientMatch.Groups["ingredient"].Value, quantity);
 
                                    }
 
                                    else
 
                                    {
 
                                        success = false;
 
                                    }
 
                                }
 
                                break;
 
                            case "SearchNodes":
 
                                int nodeCount = int.Parse(match.Groups["value"].Value);
 
                                for (int i = 0; i < nodeCount; ++i)
 
                                {
 
                                    SearchNode node = new SearchNode(costSortedReagents);
 
                                    success = success && node.LoadState(reader);
 
                                    if (success)
 
                                    {
 
                                        searchQueue.Enqueue(node);
 
                                    }
 
                                }
 
                                break;
 
                            default:
 
                                success = false;
 
                                break;
 
                        }
 
                    }
 
                    else
 
                    {
 
                        success = false;
 
                        break;
 
                    }
 
                }
 
                return success;
 
            }
 
        }
 

	
 
        private void Generate()
 
        {
 
            SearchNode node;
 

	
 
            lock(workerLock)
 
            {
 
                ++runningThreads;
 
            }
 

	
 
            bool ok = true;
 
            do
 
            {
 
                lock (workerLock)
 
                {
 
                    ok = searchQueue.TryDequeue(out node);
 
                }
 
                if (ok)
 
                {
 
                    uint targetQuantity = node.MaxQuantity + 1;
 
                    do {
 
                        --targetQuantity;
 
                        node.InitForQuantity(targetQuantity);
 
                    } while (targetQuantity > 10 && (node.CurrentTargetQuantity != node.UsedQuantity));
 

	
 
                    while (ok = Iterate(node) && !requestCancel)
 
                    while ((ok = Iterate(node)) && !requestCancel)
 
                    {
 
                        if (Progress != null)
 
                        {
 
                            Progress(this, null);
 
                        }
 
                    }
 
                    if (ok)
 
                    {
...
 
@@ -446,27 +739,29 @@ namespace DesertPaintLab
 
                        searchQueue.Enqueue(node);
 
                    }
 
                }
 
            } while (!requestCancel && ok);
 

	
 
            bool done = false;
 
            lock(workerLock)
 
            {
 
                generatorThreads.Remove(Thread.CurrentThread);
 
                --runningThreads;
 
                //generatorThreads.Remove(Thread.CurrentThread);
 

	
 
                done = (generatorThreads.Count == 0);
 
                done = (runningThreads == 0);
 
            }
 
            if (done)
 
            {
 
                running = false;
 
                requestCancel = false;
 
                if (Finished != null)
 
                {
 
                    Finished(this, null);
 
                }
 
                running = false;
 
            }
 
        }
 

	
 
        // Add the cheapest recipe to the recipe list
 
        // returns the discarded recipe from the pair (or null if no original recipe to replace)
 
        private PaintRecipe AddCheapestRecipe(PaintRecipe recipe)
 
        {
 
            PaintRecipe discarded = recipe;
...
 
@@ -693,22 +988,23 @@ namespace DesertPaintLab
 
                return false;
 
            }
 

	
 
            return true;
 
        }
 

	
 
        public void Wait()
 
        {
 
            if (running)
 
            if (generatorThreads.Count > 0)
 
            {
 
                foreach (Thread thr in generatorThreads)
 
                {
 
                    thr.Join();
 
                }
 
                generatorThreads.Clear();
 
            }
 
        }
 

	
 
        public void Stop()
 
        {
 
            this.requestCancel = true;
 
        }
 

	
RecipeGeneratorWindow.cs
Show inline comments
...
 
@@ -26,23 +26,30 @@ using System.Collections.Generic;
 
namespace DesertPaintLab
 
{
 
    public partial class RecipeGeneratorWindow : Gtk.Window
 
    {
 
        RecipeGenerator generator;
 
        PlayerProfile profile;
 
        bool canceling = false;
 
        bool running = false;
 
        bool pauseForCheckpoint = false;
 

	
 
        const long RECIPE_SAVE_INTERVAL = 30000; // msec between saving recipes
 
        const long CHECKPOINT_INTERVAL = 17000; // msec between saving out generator state
 
        const string STATE_FILE = "dp_generator_state";
 

	
 
        static Gtk.ListStore colorStore = new Gtk.ListStore(typeof(string));
 

	
 
        List<KeyValuePair<string, string>> missingReactions = new List<KeyValuePair<string, string>>();
 

	
 
        long lastProgressUpdate;
 
        long lastStatusUpdate;
 
        long lastProfileSave;
 
        long lastCheckpoint;
 

	
 
        static public Gtk.ListStore RecipeModel
 
        {
 
            get
 
            {
 
                return colorStore;   
 
            }
 
        }
...
 
@@ -62,32 +69,56 @@ namespace DesertPaintLab
 
            Gtk.TreeViewColumn recipeColorColumn = new Gtk.TreeViewColumn();
 
            Gtk.CellRendererText recipeColumnCell = new Gtk.CellRendererText();
 
            recipeColorColumn.PackStart(recipeColumnCell, true);       
 
            recipeColorColumn.Title = "Color";
 

	
 
            recipeList.AppendColumn(recipeColorColumn);
 
            recipeColorColumn.AddAttribute(recipeColumnCell, "text", 0);
 

	
 
            colorStore.Clear();
 

	
 
            colorStore.SetSortColumnId(0, Gtk.SortType.Ascending);
 
            recipeList.Model = RecipeModel;
 

	
 
            recipeList.Selection.Changed += OnColorSelected;
 

	
 
            profile.LoadRecipes();
 

	
 
            // init UI
 
            foreach (string key in profile.Recipes.Keys)
 
            {
 
                colorStore.AppendValues(key);
 
            }
 

	
 
            countLabel.Text = String.Format("{0} / {1}", profile.Recipes.Count, Palette.Count);
 
            canceling = false;
 
            running = false;
 
            pauseForCheckpoint = false;
 

	
 
            generator = new RecipeGenerator(profile.Reactions);
 
            generator.InitRecipes(profile.Recipes);
 

	
 
            generator.Progress += OnProgress;
 
            generator.Finished += OnFinished;
 
            generator.NewRecipe += OnNewRecipe;
 

	
 
            string stateFile = System.IO.Path.Combine(profile.Directory, STATE_FILE);
 
            if (System.IO.File.Exists(stateFile))
 
            {
 
                generator.LoadState(stateFile);
 
                if (generator.CanResume)
 
                {
 
                    beginButton.Label = "Restart";
 
                    stopResumeButton.Label = "Resume";
 
                    stopResumeButton.Sensitive = true;
 
                }
 
            }
 
            countLabel.Text = String.Format("{0} / {1}", generator.Recipes.Count, Palette.Count);
 

	
 
            Destroyed += OnDestroyed;
 
        }
 

	
 
        protected void OnMaxIngredientsChanged(object sender, EventArgs e)
 
        {
 
            // TODO: save profile setting
 
            // TODO: no longer permit resume
 
        }
 

	
...
 
@@ -107,32 +138,26 @@ namespace DesertPaintLab
 
        {
 
            // TODO: save profile setting
 
            // TODO: no longer permit resume
 
        }
 

	
 
        protected void OnBegin(object sender, EventArgs e)
 
        {
 
            maxIngredientsSpinButton.Sensitive = false;
 
            ExportAction.Sensitive = false;
 
            SettingsAction.Sensitive = false;
 
            ExportToWikiAction.Sensitive = false;
 
            IngredientsAction.Sensitive = false;
 
            maxRecipeSpinButton.Sensitive = false;
 
            beginButton.Sensitive = false; // TODO: change to "pause"?
 
            stopResumeButton.Sensitive = true;
 
            fullQuantitySpinButton.Sensitive = false;
 
            fullQuantityDepthSpinButton.Sensitive = false;
 

	
 
            generator = new RecipeGenerator();
 

	
 
            generator.InitRecipes(profile.Recipes, profile.Reactions);
 
            countLabel.Text = String.Format("{0} / {1}", generator.Recipes.Count, Palette.Count);
 

	
 
            generator.Progress += OnProgress;
 
            generator.Finished += OnFinished;
 
            generator.NewRecipe += OnNewRecipe;
 
            // TODO: hook up event notifications
 
            // - progress
 
            // - complete
 
            // - new recipe / recipe update
 

	
 
            // Total recipe search count
 
            //int current = ReagentManager.Names.Count;
 
            //long recipePermutations = 1;
...
 
@@ -141,64 +166,91 @@ namespace DesertPaintLab
 
            //    recipePermutations *= current;
 
            //    --current;
 
            //}
 
            //System.Console.WriteLine("Will search {0} reagent permutations.", recipePermutations);
 

	
 
            lastProgressUpdate = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
 
            lastStatusUpdate = lastProgressUpdate;
 

	
 
            lastProfileSave = lastProgressUpdate;
 
            lastCheckpoint = lastProgressUpdate;
 

	
 
            running = true;
 
            stopResumeButton.Label = "Stop";
 
            canceling = false;
 
            pauseForCheckpoint = false;
 
            stopResumeButton.Label = "Pause";
 

	
 
            generator.BeginRecipeGeneration(profile.Reactions, (uint)maxRecipeSpinButton.ValueAsInt, (uint)maxIngredientsSpinButton.ValueAsInt, (uint)fullQuantityDepthSpinButton.ValueAsInt, (uint)fullQuantitySpinButton.ValueAsInt);
 
            generator.BeginRecipeGeneration((uint)maxRecipeSpinButton.ValueAsInt, (uint)maxIngredientsSpinButton.ValueAsInt, (uint)fullQuantityDepthSpinButton.ValueAsInt, (uint)fullQuantitySpinButton.ValueAsInt);
 
        }
 

	
 
        protected void OnStopResume(object sender, EventArgs e)
 
        {
 
            if (generator != null)
 
            {
 
                if (running)
 
                {
 
                    canceling = true;
 
                    pauseForCheckpoint = false;
 
                    generator.Stop();
 
                }
 
                else
 
                {
 
                    // this must be a resume
 
                    // Resume previous run
 
                    ExportToWikiAction.Sensitive = false;
 
                    IngredientsAction.Sensitive = false;
 
                    lastProgressUpdate = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
 
                    lastStatusUpdate = lastProgressUpdate;
 
                    lastProfileSave = lastProgressUpdate;
 
                    lastCheckpoint = lastProgressUpdate;
 

	
 
                    canceling = false;
 
                        pauseForCheckpoint = false;
 
                    running = true;
 
        
 
                    stopResumeButton.Label = "Stop";
 
                    generator.BeginRecipeGeneration(profile.Reactions, (uint)maxRecipeSpinButton.ValueAsInt, (uint)maxIngredientsSpinButton.ValueAsInt, (uint)fullQuantityDepthSpinButton.ValueAsInt, (uint)fullQuantitySpinButton.ValueAsInt);
 
                    stopResumeButton.Label = "Pause";
 
                    generator.ResumeRecipeGeneration();
 
                }
 
            }
 
        }
 

	
 
        protected void OnFinished(object sender, EventArgs args)
 
        {
 
            Gtk.Application.Invoke(delegate {
 
                running = false;
 
                beginButton.Sensitive = true;
 
                ExportAction.Sensitive = true;
 
                SettingsAction.Sensitive = true;
 
                stopResumeButton.Sensitive = false;
 
                maxIngredientsSpinButton.Sensitive = true;
 
                maxRecipeSpinButton.Sensitive = true;
 
                fullQuantitySpinButton.Sensitive = true;
 
                fullQuantityDepthSpinButton.Sensitive = true;
 
                //generator = null; // don't. Hang on to generator for resume.
 
                profile.SaveRecipes();
 
                if (canceling)
 
                generator.Wait();
 
                if (pauseForCheckpoint)
 
                {
 
                    pauseForCheckpoint = false;
 
                    generator.SaveState(System.IO.Path.Combine(profile.Directory, STATE_FILE));
 
                    generator.ResumeRecipeGeneration();
 
                }
 
                else
 
                {
 
                    stopResumeButton.Label = "Resume";
 
                    stopResumeButton.Sensitive = true;
 
                    running = false;
 
                    beginButton.Sensitive = true;
 
                    ExportToWikiAction.Sensitive = true;
 
                    IngredientsAction.Sensitive = true;
 
                    stopResumeButton.Sensitive = false;
 
                    maxIngredientsSpinButton.Sensitive = true;
 
                    maxRecipeSpinButton.Sensitive = true;
 
                    fullQuantitySpinButton.Sensitive = true;
 
                    fullQuantityDepthSpinButton.Sensitive = true;
 
                    //generator = null; // don't. Hang on to generator for resume.
 
                    profile.SaveRecipes();
 
                    if (canceling)
 
                    {
 
                        generator.SaveState(System.IO.Path.Combine(profile.Directory, STATE_FILE));
 
                        stopResumeButton.Label = "Resume";
 
                        stopResumeButton.Sensitive = true;
 
                        beginButton.Label = "Restart";
 
                    }
 
                    else
 
                    {
 
                        System.IO.File.Delete(System.IO.Path.Combine(profile.Directory, STATE_FILE));
 
                    }
 
                }
 
            });
 
        }
 

	
 
        protected void OnNewRecipe(object sender, NewRecipeEventArgs args)
 
        {
 
            PaintRecipe recipe = new PaintRecipe(args.Recipe); // copy it
 
            Gtk.Application.Invoke(delegate
...
 
@@ -223,16 +275,24 @@ namespace DesertPaintLab
 
                {
 
                    //Console.WriteLine("Add new recipe for {0}", args.Color);
 
                    //    bool isMissingReactions = args.Recipe.CheckMissingReactions(ref missingReactions);
 
                    //    string missingReactionLabel = isMissingReactions ? "X" : "";
 
                    colorStore.AppendValues(args.Color); // , missingReactionLabel);
 
                    countLabel.Text = String.Format("{0} / {1}", generator.Recipes.Count, Palette.Count);
 
                }
 
                profile.SetRecipe(recipe);
 

	
 
                long progressTime = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
 
                long delta = progressTime - lastProfileSave;
 
                if (delta >= RECIPE_SAVE_INTERVAL)
 
                {
 
                    profile.SaveRecipes();
 
                    lastProfileSave = progressTime;
 
                }
 
            });
 
        }
 

	
 
        protected void OnProgress(object sender, EventArgs args)
 
        {
 
            Gtk.Application.Invoke(delegate
 
            {
 
                // TODO: based on time rather than count
...
 
@@ -246,16 +306,24 @@ namespace DesertPaintLab
 
                delta = progressTime - lastStatusUpdate;
 
                if (delta > 500)
 
                {
 
                    // Update recipe count label as well
 
                    statusLabel.Text = String.Format("Recipes searched: {0:N00}", generator.RecipeCount);
 
                    lastStatusUpdate = progressTime;
 
                }
 
                //progressBar.Fraction = (double)((generator.RecipeCount / 10000) % 100) / 100.0;
 

	
 
                delta = progressTime - lastCheckpoint;
 
                if (delta > CHECKPOINT_INTERVAL)
 
                {
 
                    pauseForCheckpoint = true;
 
                    lastCheckpoint = progressTime;
 
                    generator.Stop();
 
                }
 
            });
 
        }
 

	
 
        protected void OnColorSelected(object o, EventArgs args)
 
        {
 
            Gtk.TreeModel model;
 
            Gtk.TreeIter iter;
 
            Gtk.TreeSelection selection = recipeList.Selection;
...
 
@@ -301,11 +369,25 @@ namespace DesertPaintLab
 
            fileDialog.Destroy();
 
        }
 

	
 
        protected void OnShowIngredients(object sender, EventArgs e)
 
        {
 
            ReagentWindow win = new ReagentWindow(profile);
 
            win.Show();
 
        }
 

	
 
        protected void OnDestroyed(object o, EventArgs args)
 
        {
 
            if (running)
 
            {
 
                // window closed while generator running: stop and save
 
                generator.Finished -= OnFinished;
 
                generator.Progress -= OnProgress;
 
                generator.NewRecipe -= OnNewRecipe;
 
                generator.Stop();
 
                generator.Wait();
 
                generator.SaveState(System.IO.Path.Combine(profile.Directory, STATE_FILE));
 
            }
 
        }
 
    }
 
}
 

	
gtk-gui/gui.stetic
Show inline comments
 
<?xml version="1.0" encoding="utf-8"?>
 
<stetic-interface>
 
  <configuration>
 
    <images-root-path>..</images-root-path>
 
    <target-gtk-version>2.12</target-gtk-version>
 
  </configuration>
 
  <import>
 
    <widget-library name="glade-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
 
    <widget-library name="../bin/Debug/DesertPaintLab.exe" internal="true" />
 
    <widget-library name="../bin/Release/DesertPaintLab.exe" internal="true" />
 
  </import>
 
  <widget class="Gtk.Window" id="MainWindow" design-size="629 265">
 
    <action-group name="Default">
 
      <action id="FileAction">
 
        <property name="Type">Action</property>
 
        <property name="Accelerator">&lt;Alt&gt;f</property>
 
        <property name="Label" translatable="yes">_File</property>
 
        <property name="ShortLabel" translatable="yes">_File</property>
mac/build-mac-bundle.sh
Show inline comments
...
 
@@ -14,13 +14,13 @@ fi
 
/bin/cp launcher.sh bin/DesertPaintLab.app/Contents/MacOS
 
/bin/chmod 755 bin/DesertPaintLab.app/Contents/MacOS/launcher.sh
 
/bin/cp ../bin/Release/DesertPaintLab.exe bin/DesertPaintLab.app/Contents/MacOS/
 
/bin/mkdir -p bin/DesertPaintLab.app/Contents/Resources
 
/bin/cp ../bin/Release/colors.txt bin/DesertPaintLab.app/Contents/Resources/
 
/bin/cp ../bin/Release/ingredients.txt bin/DesertPaintLab.app/Contents/Resources/
 
/bin/cp -r ../bin/Release/template bin/DesertPaintLab.app/Contents/Resources/template
 

	
 
/usr/bin/defaults write bin/DesertPaintLab.app/Info.plist CFBundleShortVersionString ${VERSION}
 
/usr/bin/defaults write bin/DesertPaintLab.app/Info.plist CFBundleVersion ${VERSION}
 
/usr/bin/defaults write `pwd`/bin/DesertPaintLab.app/Contents/Info.plist CFBundleShortVersionString ${VERSION}
 
/usr/bin/defaults write `pwd`/bin/DesertPaintLab.app/Contents/Info.plist CFBundleVersion ${VERSION}
 

	
 
# package up into a DMG
 
/usr/bin/hdiutil create -volname DesertPaintLab -srcfolder bin/ -ov -format UDIF -fs HFS+ DesertPaintLab.dmg
0 comments (0 inline, 0 general)