try to handle UI responsiveness by interrupting loadings instead of recursively trigg...
authorGuillaume Cottenceau <gcottenc@gmail.com>
Thu, 11 Jun 2009 12:25:28 +0000 (14:25 +0200)
committerGuillaume Cottenceau <gcottenc@gmail.com>
Thu, 11 Jun 2009 12:25:28 +0000 (14:25 +0200)
bin/booh-classifier
ext/rbbooh.cc

index 64ed74f..c229fe2 100755 (executable)
@@ -196,8 +196,8 @@ def save_undo(name, closure, *params)
 end
 
 def get_mem
-    IO.readlines('/proc/self/status').join =~ /VmRSS.*?(\d+)\s*kB/
-    msg 3, "RSS: #{$1}"
+    IO.readlines('/proc/self/status').join =~ /VmSize.*?(\d+)\s*kB/
+    msg 3, "VmSize: #{$1}"
     return $1.to_i
 end
 
@@ -237,6 +237,10 @@ class Label
     end
 end
 
+class InterruptedLoading < Exception
+    #- not a StandardError, not catched by a simple rescue
+end
+
 class Entry
     @@max_width = nil
     def Entry.thumbnails_height
@@ -254,13 +258,6 @@ class Entry
         end
     end
 
-    def pixbuf_full
-        if @pixbuf_full.nil?
-            msg 3, ">>> pixbuf_full #{path}"
-            load_into_pixbuf_full
-        end
-        return @pixbuf_full
-    end
     def free_pixbuf_full
         if @pixbuf_full.nil?
             return false
@@ -270,15 +267,15 @@ class Entry
             return true
         end
     end
-    def pixbuf_main(interruptable)
+    def pixbuf_main
+        Gtk.main_iteration while Gtk.events_pending?
         width, height = $mainview.window.size 
         width = MainView.get_usable_width(width)
         height = MainView.get_usable_height(height)
         if @pixbuf_main.nil? || width != @width || height != @height
-            msg 3, ">>> pixbuf_main #{path}"
             @width = width
             @height = height
-            load_into_pixbuf_full(interruptable)  #- make sure it is loaded
+            load_into_pixbuf_full  #- make sure it is loaded
             if @pixbuf_full.nil?
                 return
             end
@@ -310,13 +307,14 @@ class Entry
         end
     end
     def pixbuf_thumbnail
+        Gtk.main_iteration while Gtk.events_pending?
         if @pixbuf_thumbnail.nil?
             if @pixbuf_main
                 msg 3, ">>> pixbuf_thumbnail from main #{path}"
                 @pixbuf_thumbnail = @pixbuf_main.scale(@pixbuf_main.width * (Entry.thumbnails_height.to_f/@pixbuf_main.height), Entry.thumbnails_height, Gdk::Pixbuf::INTERP_BILINEAR)
             else
                 msg 3, ">>> pixbuf_thumbnail from file #{path}"
-                @pixbuf_thumbnail = load_into_pixbuf_at_size(false) { |w, h|
+                @pixbuf_thumbnail = load_into_pixbuf_at_size { |w, h|
                     if @angle == 0
                         if h > Entry.thumbnails_height
                             [ w * Entry.thumbnails_height.to_f/h, Entry.thumbnails_height ]
@@ -386,10 +384,10 @@ class Entry
         Dir.delete(dir)
     end
 
-    def load_into_pixbuf_full(interruptable)
+    def load_into_pixbuf_full
         if @pixbuf_full.nil?
             msg 3, ">>> load_into_pixbuf_full #{path}"
-            @pixbuf_full = load_into_pixbuf_at_size(interruptable) { |w, h|
+            @pixbuf_full = load_into_pixbuf_at_size { |w, h|
                 if @angle == 0
                     if w > @@max_width
                         #- save memory and speedup (+35%) loading 
@@ -408,7 +406,7 @@ class Entry
         end
     end
 
-    def load_into_pixbuf_at_size(interruptable, &specify_size)
+    def load_into_pixbuf_at_size(&specify_size)
         pixbuf = nil
         if @type == 'video'
             tmp = Tempfile.new("boohclassifiertemp")
@@ -441,19 +439,15 @@ class Entry
                 loader.set_size(*specify_size.call(w, h))
             }
             id = loader.signal_connect('area-prepared') { pixbuf = loader.pixbuf }
-            if ! loader.load_not_freezing_ui(image_path, interruptable, id)
-                return
+            if ! loader.load_not_freezing_ui(image_path, id)
+                #- interrupted
+                raise InterruptedLoading
             end
-            loader.close
             if pixbuf.nil?
                 raise "Loaded pixbuf nil - #{path} #{image_path}"
             end
         rescue
             msg 0, "Cannot load #{image_path}: #{$!}"
-            begin
-                loader.close
-            rescue
-            end
             return
         ensure
             if @type == 'video'
@@ -510,22 +504,16 @@ end
 
 def run_preloader_real
     msg 3, "*** >> main preloading triggered..."
-    if $preloader_running
-        msg 3, "*** >>>>>> already running, return <<<<<<<<"
-        return
-    end
-    $preloader_running = true
     if $mainview.get_shown_entry
         if get_mem > $config['cache-memory-use-figure']
             msg 3, "too much RSS, stopping preloading, triggering GC"
-            $preloader_running = false
             gc
             get_mem
-            return
+            return true
         end
         if $config['preload-distance'].to_i == 0
             free_cache([])
-            return
+            return true
         end
         index = $allentries.index($mainview.get_shown_entry)
         index_right = index
@@ -545,7 +533,12 @@ def run_preloader_real
                     right_done = true
                 else
                     msg 3, "preloading #{$allentries[index_right].path}"
-                    $allentries[index_right].pixbuf_main(false)
+                    begin
+                        $allentries[index_right].pixbuf_main
+                    rescue InterruptedLoading
+                        msg 3, "*** >>>> interrupted, rerun"
+                        return false
+                    end
                     loaded << index_right
                     loaded_right += 1
                     if loaded_right == $config['preload-distance'].to_i
@@ -563,7 +556,12 @@ def run_preloader_real
                     left_done = true
                 else
                     msg 3, "preloading #{$allentries[index_left].path}"
-                    $allentries[index_left].pixbuf_main(false)
+                    begin
+                        $allentries[index_left].pixbuf_main
+                    rescue InterruptedLoading
+                        msg 3, "*** >>>> interrupted, rerun"
+                        return false
+                    end
                     loaded << index_left
                     loaded_left += 1
                     if loaded_left == $config['preload-distance'].to_i
@@ -574,22 +572,19 @@ def run_preloader_real
 
             #- in case just loaded another directory
             if $preloader_force_exit
-                $preloader_running = false
                 $preloader_force_exit = false
-                return
+                return true
             end
             #- in case moved fast
             if index != $allentries.index($mainview.get_shown_entry)
                 msg 3, "*** >>>> moved already, rerun"
-                $preloader_running = false
-                run_preloader_real
-                return
+                return false
             end
         end
         free_cache(loaded)
     end
-    $preloader_running = false
     msg 3, "*** << main preloading finished"
+    return true
 end
 
 def run_preloader
@@ -597,9 +592,21 @@ def run_preloader
         msg 3, "*** preloader not yet allowed"
         return
     end
-    Gtk.timeout_add(10) {
-        run_preloader_real
-        false
+
+    if $preloader_running
+        msg 3, "preloader already running"
+        return
+    end
+    msg 3, "run preloader"
+    $preloader_running = true
+    Gtk.idle_add {
+        msg 3, "begin preloader from timeout "
+        if run_preloader_real
+            $preloader_running = false
+            false
+        else
+            true
+        end
     }
 end
 
@@ -607,6 +614,7 @@ class MainView < Gtk::DrawingArea
 
     @@borders_thickness = 5
     @@borders_length = 25
+    @@redraw_pending = nil
 
     def MainView.borders_thickness
         return @@borders_thickness
@@ -646,8 +654,8 @@ class MainView < Gtk::DrawingArea
             return
         end
         @entry = entry
+        @entry and msg 3, "*** set entry to #{@entry.path}"
         redraw
-        run_preloader
         msg 3, "entry shown in: #{Time.now - t1} s"
     end
 
@@ -672,6 +680,27 @@ class MainView < Gtk::DrawingArea
     end
 
     def redraw
+        if @@redraw_pending
+            msg 3, "redraw already pending"
+            return
+        end
+        msg 3, "redraw"
+        @@redraw_pending = Gtk.idle_add {
+            msg 3, "begin redraw from timeout "
+            begin
+                msg 3, "try redraw from timeout"
+                redraw_real
+                @@redraw_pending = nil
+                run_preloader
+                false
+            rescue InterruptedLoading
+                msg 3, "interrupted, will retry"
+                true
+            end
+        }
+    end
+
+    def redraw_real
         @entry and sb_msg(_("Selected %s") % @entry.get_beautified_name)
         if ! update_shown
             return
@@ -681,14 +710,12 @@ class MainView < Gtk::DrawingArea
         window.clear
         draw
         window.end_paint
-        Gtk.main_iteration while Gtk.events_pending?
     end
 
     def update_shown
         if @entry
-            $interrupt_loading = false
-            pixbuf = @entry.pixbuf_main(true)
-            $interrupt_loading = true
+            msg 3, "################################################ trying to show #{@entry.path}"
+            pixbuf = @entry.pixbuf_main
             if pixbuf
                 @pixbuf = pixbuf
                 width, height = window.size 
@@ -1085,7 +1112,6 @@ def show_entry(entry, i, tips)
         entry.button.grab_focus
     end
     update_visibility(entry)
-    Gtk.main_iteration while Gtk.events_pending?
 end
 
 def show_entries(allentries)
@@ -1099,13 +1125,17 @@ def show_entries(allentries)
     i = 0
     tips = Gtk::Tooltips.new
     while i < allentries.size
-#        printf "%d %s\n", i, __LINE__
-        entry = allentries[i]
-        if i == 0
-            loaded_pixbuf = entry.pixbuf_main(false)
-        else
-            loaded_pixbuf = entry.pixbuf_thumbnail
+        begin
+            entry = allentries[i]
+            if i == 0
+                loaded_pixbuf = entry.pixbuf_main
+            else
+                loaded_pixbuf = entry.pixbuf_thumbnail
+            end
+        rescue InterruptedLoading
+            redo
         end
+
         if $allentries != allentries
             #- loaded another directory while this one was not yet finished
             msg 3, "allentries differ, stopping this deprecated load"
@@ -1124,8 +1154,11 @@ def show_entries(allentries)
             total_loaded_files += 1
             i += 1
             if i > $config['preload-distance'].to_i && i <= $config['preload-distance'].to_i * 2
-                #- when we're at preload distance, beging preloading to preload distance
-                allentries[i - $config['preload-distance'].to_i].pixbuf_main(false)
+                #- when we're at preload distance, begin preloading to preload distance
+                begin
+                    allentries[i - $config['preload-distance'].to_i].pixbuf_main
+                rescue InterruptedLoading
+                end
             end
             if i == $config['preload-distance'].to_i * 2 + 1
                 #- when we're after double preload distance, activate normal preloading
index 4827ae8..9becd2f 100644 (file)
@@ -175,46 +175,36 @@ static VALUE modify_bg(VALUE self, VALUE state, VALUE color) {
         return self;
 }
 
-static int counter = 0;
-
 // internalize pixbuf loading for 30% more speedup
-static VALUE load_not_freezing_ui(VALUE self, VALUE path, VALUE interruptable, VALUE area_prepared_signal_id) {
-        int interruptable_ = RTEST(interruptable);
+static VALUE load_not_freezing_ui(VALUE self, VALUE path, VALUE area_prepared_signal_id) {
         char buf[65536];
         size_t amount;
         GdkPixbufLoader* loader = GDK_PIXBUF_LOADER(RVAL2GOBJ(self));
         GError* error = NULL;
         FILE* f = fopen(RVAL2CSTR(path), "r");
         if (!f) {
+                gdk_pixbuf_loader_close(loader, NULL);
                 rb_raise(rb_eRuntimeError, "Unable to open file %s for reading", RVAL2CSTR(path));
         }
-        counter++;
         while ((amount = fread(buf, 1, 65536, f)) > 0) {
                 if (!gdk_pixbuf_loader_write(loader, (const guchar*) buf, amount, &error)) {
+                        gdk_pixbuf_loader_close(loader, NULL);
                         fclose(f);
                         RAISE_GERROR(error);
                 }
-                if (counter < 52) {
-                        // if the user scrolls too fast between thumbnails, there can be large amount of recursions,
-                        // and program may abort - probably on exhausted stack size; this figure is completely empiric
-                        while (gtk_events_pending()) {
-                                gtk_main_iteration();
-                        }
-                        if (interruptable_ && RTEST(rb_eval_string("$interrupt_loading"))) {
-                                // interrupted, case when the user clicked/keyboarded too quickly for this image to
-                                // display; we cancel this loading, but before we disconnect the area-prepared
-                                // signal handler, else the call to gdk_pixbuf_loader_get_pixbuf will take ages
-                                // (between 100 and 400 ms on my p4 for a large photo!)
-                                g_signal_handler_disconnect(loader, NUM2INT(area_prepared_signal_id));
-                                gdk_pixbuf_loader_close(loader, NULL);
-                                fclose(f);
-                                counter--;
-                                return Qfalse;
-                        }
+                if (gtk_events_pending()) {
+                        // interrupted, case when the user clicked/keyboarded too quickly for this image to
+                        // display; we cancel this loading, but before we disconnect the area-prepared
+                        // signal handler, else the call to gdk_pixbuf_loader_get_pixbuf will take ages
+                        // (between 100 and 400 ms on my p4 for a large photo!)
+                        g_signal_handler_disconnect(loader, NUM2INT(area_prepared_signal_id));
+                        gdk_pixbuf_loader_close(loader, NULL);
+                        fclose(f);
+                        return Qfalse;
                 }
         }
+        gdk_pixbuf_loader_close(loader, NULL);
         fclose(f);
-        counter--;
         return Qtrue;
 }
 
@@ -234,7 +224,7 @@ Init_libadds()
     rb_define_method(cinfo->klass, "modify_bg", (VALUE (*)(...)) modify_bg, 2);
 
     cinfo = (RGObjClassInfo*)rbgobj_lookup_class_by_gtype(GDK_TYPE_PIXBUF_LOADER, Qnil);
-    rb_define_method(cinfo->klass, "load_not_freezing_ui", (VALUE (*)(...)) load_not_freezing_ui, 3);
+    rb_define_method(cinfo->klass, "load_not_freezing_ui", (VALUE (*)(...)) load_not_freezing_ui, 2);
 
     VALUE exif = rb_define_module("Exif");
     rb_define_module_function(exif, "orientation", (VALUE (*)(...)) exif_orientation, 1);