[gnome-sudoku] SudokuGenerator: Use multiple threads for puzzle generation



commit 1e7d1ec53162b23a5bb809756e66103c7271d3e1
Author: Parin Porecha <parinporecha gmail com>
Date:   Mon Aug 18 19:34:22 2014 +0200

    SudokuGenerator: Use multiple threads for puzzle generation
    
    https://bugzilla.gnome.org/show_bug.cgi?id=580056

 configure.ac              |    2 +
 data/print-games.ui       |    9 +++++
 lib/Makefile.am           |    1 +
 lib/sudoku-generator.vala |   75 +++++++++++++++++++++++++++++++++++++++++++++
 src/gnome-sudoku.vala     |   10 +++++-
 src/sudoku-printer.vala   |   39 +++++++++++++++++------
 6 files changed, 124 insertions(+), 12 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 96073d2..3394e8d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -42,6 +42,8 @@ PKG_CHECK_MODULES(LIBSUDOKU, [
   json-glib-1.0
 ])
 
+AC_SUBST([GLIB_REQUIRED])
+
 AC_PATH_PROG([APPDATA_VALIDATE], [appdata-validate], [/bin/true])
 AC_PATH_PROG([DESKTOP_FILE_VALIDATE], [desktop-file-validate], [/bin/true])
 
diff --git a/data/print-games.ui b/data/print-games.ui
index 62a5ef1..d204313 100644
--- a/data/print-games.ui
+++ b/data/print-games.ui
@@ -54,6 +54,15 @@
             <property name="pack_type">start</property>
           </packing>
         </child>
+        <child>
+            <object class="GtkSpinner" id="spinner">
+                <property name="valign">center</property>
+                <property name="visible">False</property>
+            </object>
+            <packing>
+                <property name="pack_type">end</property>
+            </packing>
+        </child>
       </object>
     </child>
     <child internal-child="vbox">
diff --git a/lib/Makefile.am b/lib/Makefile.am
index e0af6eb..b994bb4 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -26,6 +26,7 @@ libsudoku_la_VALAFLAGS = \
        --pkg gio-2.0 \
        --pkg json-glib-1.0 \
        --pkg posix \
+       --target-glib=$(GLIB_REQUIRED)  \
        --header=libsudoku.h \
        --vapi=libsudoku.vapi
 
diff --git a/lib/sudoku-generator.vala b/lib/sudoku-generator.vala
index c53a406..100f6f6 100644
--- a/lib/sudoku-generator.vala
+++ b/lib/sudoku-generator.vala
@@ -1,5 +1,7 @@
 /* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 
+using Gee;
+
 public class SudokuGenerator : Object
 {
     private SudokuGenerator () {
@@ -22,6 +24,38 @@ public class SudokuGenerator : Object
         return board;
     }
 
+    public async static SudokuBoard[] generate_boards_async (int nboards, DifficultyCategory category) 
throws ThreadError
+    {
+        var boards_list = new ArrayList<SudokuBoard> ();
+        var boards = new SudokuBoard[nboards];
+        Thread<void*> threads[16];
+
+        var ncpu_usable = int.max (1, get_number_of_processors () - 1);
+        var nthreads = int.min (ncpu_usable, nboards);
+        var base_nsudokus_each = nboards / nthreads;
+        var remainder = nboards % nthreads;
+        var nsudokus_per_thread = base_nsudokus_each;
+
+        for (var i = 0; i < nthreads; i++)
+        {
+            if (i > (nthreads - remainder - 1))
+                nsudokus_per_thread = base_nsudokus_each + 1;
+            var gen_thread = new GeneratorThread (nsudokus_per_thread, category, ref boards_list, 
generate_boards_async.callback);
+            threads[i] = new Thread<void*> ("Generator thread", gen_thread.run);
+        }
+
+        // Relinquish the CPU, so that the generated threads can run
+        for (var i = 0; i < nthreads; i++)
+        {
+            yield;
+            threads[i].join ();
+        }
+
+        for (var i = 0; i < boards_list.size; i++)
+            boards[i] = boards_list[i];
+        return boards;
+    }
+
     public static void print_stats (SudokuBoard board)
     {
         var cells = board.get_cells ();
@@ -38,4 +72,45 @@ public class SudokuGenerator : Object
     {
         return QQwing.get_version ();
     }
+
+    private static int get_number_of_processors ()
+    {
+        int ncpu;
+        string nproc_stdout;
+
+        try {
+            Process.spawn_command_line_sync ("nproc", out nproc_stdout);
+            ncpu = int.parse (nproc_stdout);
+        } catch (SpawnError e) {
+            warning ("Call to nproc failed. Puzzles will be generated in a single thread");
+            ncpu = 1;
+        }
+
+        return ncpu;
+    }
+}
+
+public class GeneratorThread : Object
+{
+    private int nsudokus;
+    private DifficultyCategory level;
+    private ArrayList<SudokuBoard> boards_list;
+    private SourceFunc callback;
+
+    public GeneratorThread (int nsudokus, DifficultyCategory level, ref ArrayList<SudokuBoard> boards_list, 
SourceFunc callback)
+    {
+        this.nsudokus = nsudokus;
+        this.level = level;
+        this.boards_list = boards_list;
+        this.callback = callback;
+    }
+
+    public void* run ()
+    {
+        for (var i = 0; i < nsudokus; i++)
+            boards_list.add (SudokuGenerator.generate_board (level));
+
+        Idle.add((owned) callback);
+        return null;
+    }
 }
diff --git a/src/gnome-sudoku.vala b/src/gnome-sudoku.vala
index 2b7e860..c27542c 100644
--- a/src/gnome-sudoku.vala
+++ b/src/gnome-sudoku.vala
@@ -296,7 +296,15 @@ public class Sudoku : Gtk.Application
         // has been set to integers corresponding to the enums.
         // Following line converts those ints to their DifficultyCategory
         var selected_difficulty = (DifficultyCategory) difficulty.get_int32 ();
-        start_game (SudokuGenerator.generate_board (selected_difficulty));
+
+        SudokuGenerator.generate_boards_async.begin (1, selected_difficulty, (obj, res) => {
+            try {
+                var gen_boards = SudokuGenerator.generate_boards_async.end (res);
+                start_game (gen_boards[0]);
+            } catch (ThreadError e) {
+                error ("Thread error: %s", e.message);
+            }
+        });
     }
 
     private void reset_cb ()
diff --git a/src/sudoku-printer.vala b/src/sudoku-printer.vala
index 5ccd2f3..1abbe98 100644
--- a/src/sudoku-printer.vala
+++ b/src/sudoku-printer.vala
@@ -229,6 +229,8 @@ public class GamePrinter: GLib.Object
     private RadioButton intermediate_button;
     private RadioButton expert_button;
 
+    private Spinner spinner;
+
     private const string DIFFICULTY_KEY_NAME = "print-multiple-sudoku-difficulty";
 
     public GamePrinter (SudokuSaver saver, ref ApplicationWindow window)
@@ -279,6 +281,8 @@ public class GamePrinter: GLib.Object
 
         nsudokus_button = builder.get_object ("sudokusToPrintSpinButton") as SpinButton;
         wrap_adjustment ("print-multiple-sudokus-to-print", nsudokus_button.get_adjustment ());
+
+        spinner = builder.get_object ("spinner") as Spinner;
     }
 
     private void wrap_adjustment (string key_name, Adjustment action)
@@ -297,7 +301,6 @@ public class GamePrinter: GLib.Object
 
         var nsudokus = (int) nsudokus_button.get_adjustment ().get_value ();
         DifficultyCategory level;
-        var boards = new SudokuBoard[nsudokus];
 
         if (simple_button.get_active ())
             level = DifficultyCategory.SIMPLE;
@@ -312,18 +315,32 @@ public class GamePrinter: GLib.Object
 
         settings.set_enum (DIFFICULTY_KEY_NAME, level);
 
-        for (var i = 0; i < nsudokus; i++)
-            boards[i] = SudokuGenerator.generate_board (level);
+        spinner.visible = true;
+        spinner.active = true;
+        spinner.show ();
+        spinner.start ();
+        dialog.sensitive = false;
 
-        SudokuPrinter printer = new SudokuPrinter (boards, ref window);
+        SudokuGenerator.generate_boards_async.begin(nsudokus, level, (obj, res) => {
+            try {
+                var boards = SudokuGenerator.generate_boards_async.end(res);
 
-        PrintOperationResult result = printer.print_sudoku ();
-        if (result == PrintOperationResult.APPLY)
-        {
-            dialog.hide ();
-            foreach (SudokuBoard board in boards)
-                saver.add_game_to_finished (new SudokuGame (board));
-        }
+                spinner.stop ();
+                spinner.hide ();
+                dialog.sensitive = true;
+
+                SudokuPrinter printer = new SudokuPrinter (boards, ref window);
+                PrintOperationResult result = printer.print_sudoku ();
+                if (result == PrintOperationResult.APPLY)
+                {
+                    dialog.hide ();
+                    foreach (SudokuBoard board in boards)
+                        saver.add_game_to_finished (new SudokuGame (board));
+                }
+            } catch (ThreadError e) {
+                error ("Thread error: %s\n", e.message);
+            }
+        });
     }
 
     public void run_dialog ()


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]