Don't make timeline data processing block the GUI

This commit is contained in:
René Stadler 2007-11-21 17:40:31 +02:00 committed by Stefan Sauer
parent 5a0fff722b
commit 7cda31f6c7
2 changed files with 144 additions and 60 deletions

View file

@ -30,6 +30,10 @@ class Dispatcher (object):
raise NotImplementedError ("derived classes must override this method") raise NotImplementedError ("derived classes must override this method")
def cancel (self):
pass
class DefaultDispatcher (Dispatcher): class DefaultDispatcher (Dispatcher):
def __call__ (self, iterator): def __call__ (self, iterator):
@ -51,3 +55,11 @@ class GSourceDispatcher (Dispatcher):
gobject.source_remove (self.source_id) gobject.source_remove (self.source_id)
self.source_id = gobject.idle_add (iterator.next) self.source_id = gobject.idle_add (iterator.next)
def cancel (self):
if self.source_id is None:
return
gobject.source_remove (self.source_id)
self.source_id = None

View file

@ -1,7 +1,7 @@
import logging import logging
from GstDebugViewer import Data, GUI from GstDebugViewer import Common, Data, GUI
from GstDebugViewer.Plugins import * from GstDebugViewer.Plugins import *
import cairo import cairo
@ -18,10 +18,15 @@ class LineFrequencySentinel (object):
def __init__ (self, model): def __init__ (self, model):
self.model = model self.model = model
self.clear ()
def clear (self):
self.data = None self.data = None
self.n_partitions = None
self.partitions = None self.partitions = None
self.step = None self.step = None
self.ts_range = (None, None,) self.ts_range = (None, None,)
def _search_ts (self, target_ts, first_index, last_index): def _search_ts (self, target_ts, first_index, last_index):
@ -46,6 +51,10 @@ class LineFrequencySentinel (object):
if n == 0: if n == 0:
raise ValueError ("illegal value for n") raise ValueError ("illegal value for n")
self.n_partitions = n
def process (self):
model = self.model model = self.model
result = [] result = []
partitions = [] partitions = []
@ -71,12 +80,19 @@ class LineFrequencySentinel (object):
if last_ts is None: if last_ts is None:
return return
step = int (float (last_ts - first_ts) / float (n)) step = int (float (last_ts - first_ts) / float (self.n_partitions))
YIELD_LIMIT = 100
limit = YIELD_LIMIT
first_index = 0 first_index = 0
target_ts = first_ts + step target_ts = first_ts + step
old_found = 0 old_found = 0
while target_ts < last_ts: while target_ts < last_ts:
limit -= 1
if limit == 0:
limit = YIELD_LIMIT
yield True
found = self._search_ts (target_ts, first_index, last_index) found = self._search_ts (target_ts, first_index, last_index)
result.append (found - old_found) result.append (found - old_found)
partitions.append (found) partitions.append (found)
@ -91,12 +107,20 @@ class LineFrequencySentinel (object):
class LevelDistributionSentinel (object): class LevelDistributionSentinel (object):
def __init__ (self, model): def __init__ (self, freq_sentinel, model):
self.freq_sentinel = freq_sentinel
self.model = model self.model = model
self.data = [] self.data = []
def run_for (self, freq_sentinel): def clear (self):
del self.data[:]
def process (self):
YIELD_LIMIT = 10000
y = YIELD_LIMIT
model_get = self.model.get_value model_get = self.model.get_value
model_next = self.model.iter_next model_next = self.model.iter_next
@ -105,10 +129,14 @@ class LevelDistributionSentinel (object):
result = [] result = []
i = 0 i = 0
partitions_i = 0 partitions_i = 0
partitions = freq_sentinel.partitions partitions = self.freq_sentinel.partitions
counts = [0] * 6 counts = [0] * 6
tree_iter = self.model.get_iter_first () tree_iter = self.model.get_iter_first ()
while tree_iter: while tree_iter:
y -= 1
if y == 0:
y = YIELD_LIMIT
yield True
level = model_get (tree_iter, id_level) level = model_get (tree_iter, id_level)
if i > partitions[partitions_i]: if i > partitions[partitions_i]:
result.append (tuple (counts)) result.append (tuple (counts))
@ -125,26 +153,87 @@ class LevelDistributionSentinel (object):
self.data = result self.data = result
yield False
class UpdateProcess (object):
def __init__ (self, freq_sentinel, dist_sentinel):
self.freq_sentinel = freq_sentinel
self.dist_sentinel = dist_sentinel
self.is_running = False
self.dispatcher = Common.Data.GSourceDispatcher ()
def __process (self):
self.is_running = True
for x in self.freq_sentinel.process ():
yield True
self.handle_sentinel_finished (self.freq_sentinel)
for x in self.dist_sentinel.process ():
yield True
self.is_running = False
self.handle_sentinel_finished (self.dist_sentinel)
self.handle_process_finished ()
yield False
def run (self):
if self.is_running:
return
self.dispatcher (self.__process ())
def abort (self):
if not self.is_running:
return
self.dispatcher.cancel ()
self.is_running = False
def handle_sentinel_finished (self, sentinel):
pass
def handle_process_finished (self):
pass
class TimelineWidget (gtk.DrawingArea): class TimelineWidget (gtk.DrawingArea):
__gtype_name__ = "GstDebugViewerTimelineWidget" __gtype_name__ = "GstDebugViewerTimelineWidget"
def __init__ (self, sentinel = None): def __init__ (self, log_model):
gtk.DrawingArea.__init__ (self) gtk.DrawingArea.__init__ (self)
self.logger = logging.getLogger ("ui.timeline") self.logger = logging.getLogger ("ui.timeline")
self.sentinel = sentinel self.freq_sentinel = LineFrequencySentinel (log_model)
self.level_dist_sentinel = None self.dist_sentinel = LevelDistributionSentinel (self.freq_sentinel, log_model)
self.process = UpdateProcess (self.freq_sentinel, self.dist_sentinel)
self.connect ("expose-event", self.__handle_expose_event) self.connect ("expose-event", self.__handle_expose_event)
self.connect ("configure-event", self.__handle_configure_event) self.connect ("configure-event", self.__handle_configure_event)
self.connect ("size-request", self.__handle_size_request) self.connect ("size-request", self.__handle_size_request)
self.process.handle_sentinel_finished = self.handle_sentinel_finished
self.process.handle_process_finished = self.handle_process_finished
def set_sentinel (self, sentinel): self.__offscreen = None
def handle_sentinel_finished (self, sentinel):
if sentinel == self.freq_sentinel:
self.__redraw ()
def handle_process_finished (self):
self.sentinel = sentinel
self.level_dist_sentinel = LevelDistributionSentinel (sentinel.model)
self.__redraw () self.__redraw ()
def __redraw (self): def __redraw (self):
@ -153,9 +242,9 @@ class TimelineWidget (gtk.DrawingArea):
return return
x, y, w, h = self.get_allocation () x, y, w, h = self.get_allocation ()
self.offscreen = gtk.gdk.Pixmap (self.window, w, h, -1) self.__offscreen = gtk.gdk.Pixmap (self.window, w, h, -1)
self.__draw (self.offscreen) self.__draw (self.__offscreen)
self.__update () self.__update ()
@ -164,17 +253,24 @@ class TimelineWidget (gtk.DrawingArea):
if not self.props.visible: if not self.props.visible:
return return
if self.__offscreen is None:
self.__redraw ()
gc = gtk.gdk.GC (self.window) gc = gtk.gdk.GC (self.window)
self.window.draw_drawable (gc, self.offscreen, 0, 0, 0, 0, -1, -1) self.window.draw_drawable (gc, self.__offscreen, 0, 0, 0, 0, -1, -1)
def update_position (self, start_ts, end_ts): def update_position (self, start_ts, end_ts):
if not self.freq_sentinel.data:
return
self.__update () self.__update ()
first_ts, last_ts = self.sentinel.ts_range first_ts, last_ts = self.freq_sentinel.ts_range
step = self.freq_sentinel.step
position1 = int (float (start_ts - first_ts) / self.sentinel.step) position1 = int (float (start_ts - first_ts) / step)
position2 = int (float (end_ts - first_ts) / self.sentinel.step) position2 = int (float (end_ts - first_ts) / step)
ctx = self.window.cairo_create () ctx = self.window.cairo_create ()
x, y, w, h = self.get_allocation () x, y, w, h = self.get_allocation ()
@ -194,8 +290,8 @@ class TimelineWidget (gtk.DrawingArea):
def find_indicative_time_step (self): def find_indicative_time_step (self):
MINIMUM_PIXEL_STEP = 32 MINIMUM_PIXEL_STEP = 32
time_per_pixel = self.sentinel.step time_per_pixel = self.freq_sentinel.step
return 32 # FIXME use self.sentinel.step and len (self.sentinel.data) return 32 # FIXME use self.freq_sentinel.step and len (self.freq_sentinel.data)
def __draw (self, drawable): def __draw (self, drawable):
@ -223,25 +319,21 @@ class TimelineWidget (gtk.DrawingArea):
ctx.line_to (x, h) ctx.line_to (x, h)
ctx.stroke () ctx.stroke ()
if self.sentinel and not self.sentinel.data: if not self.freq_sentinel.data:
if w > 15: self.logger.debug ("frequency sentinel has no data yet")
self.logger.debug ("running sentinel for width %i", w)
self.__update_sentinel_data (w)
else:
self.logger.debug ("not running sentinel: widget width too small (%i)", w)
return
if self.sentinel is None:
self.logger.debug ("not redrawing: no sentinel set")
return return
maximum = max (self.sentinel.data) maximum = max (self.freq_sentinel.data)
ctx.set_source_rgb (0., 0., 0.) ctx.set_source_rgb (0., 0., 0.)
self.__draw_graph (ctx, w, h, maximum, self.sentinel.data) self.__draw_graph (ctx, w, h, maximum, self.freq_sentinel.data)
if not self.dist_sentinel.data:
self.logger.debug ("level distribution sentinel has no data yet")
return
theme = GUI.LevelColorThemeTango () theme = GUI.LevelColorThemeTango ()
dist_data = self.level_dist_sentinel.data dist_data = self.dist_sentinel.data
def cumulative_level_counts (*levels): def cumulative_level_counts (*levels):
for level_counts in dist_data: for level_counts in dist_data:
@ -298,16 +390,6 @@ class TimelineWidget (gtk.DrawingArea):
self.__redraw () self.__redraw ()
return True return True
def __update_sentinel_data (self, width):
self.logger.debug ("updating sentinel data for width %i", width)
self.sentinel.run_for (width)
self.logger.debug ("updating level distribution data")
self.level_dist_sentinel.run_for (self.sentinel)
if not self.level_dist_sentinel.data:
self.logger.warning ("no level distribution data sensed")
self.logger.debug ("sentinel update complete")
def __handle_configure_event (self, self_, event): def __handle_configure_event (self, self_, event):
self.logger.debug ("widget size configured to %ix%i", self.logger.debug ("widget size configured to %ix%i",
@ -316,8 +398,11 @@ class TimelineWidget (gtk.DrawingArea):
if event.width < 16: if event.width < 16:
return False return False
if self.sentinel: self.process.abort ()
self.__update_sentinel_data (event.width) self.freq_sentinel.clear ()
self.dist_sentinel.clear ()
self.freq_sentinel.run_for (event.width)
self.process.run ()
return False return False
@ -353,7 +438,7 @@ class TimelineFeature (FeatureBase):
box = window.get_top_attach_point () box = window.get_top_attach_point ()
self.timeline = TimelineWidget () self.timeline = TimelineWidget (self.log_model)
self.timeline.add_events (gtk.gdk.ALL_EVENTS_MASK) # FIXME self.timeline.add_events (gtk.gdk.ALL_EVENTS_MASK) # FIXME
self.timeline.connect ("button-press-event", self.handle_timeline_button_press_event) self.timeline.connect ("button-press-event", self.handle_timeline_button_press_event)
self.timeline.connect ("motion-notify-event", self.handle_timeline_motion_notify_event) self.timeline.connect ("motion-notify-event", self.handle_timeline_motion_notify_event)
@ -366,12 +451,8 @@ class TimelineFeature (FeatureBase):
handler = self.handle_show_action_toggled handler = self.handle_show_action_toggled
self.action_group.get_action ("show-timeline").connect ("toggled", handler) self.action_group.get_action ("show-timeline").connect ("toggled", handler)
window.sentinels.append (self.sentinel_process)
def detach (self, window): def detach (self, window):
window.sentinels.remove (self.sentinel_process)
window.ui_manager.remove_ui (self.merge_id) window.ui_manager.remove_ui (self.merge_id)
self.merge_id = None self.merge_id = None
@ -380,12 +461,6 @@ class TimelineFeature (FeatureBase):
self.timeline.destroy () self.timeline.destroy ()
self.timeline = None self.timeline = None
def sentinel_process (self):
if self.action_group.get_action ("show-timeline").props.active:
sentinel = LineDensitySentinel (self.log_model)
self.timeline.set_sentinel (sentinel)
def handle_log_view_adjustment_value_changed (self, adj): def handle_log_view_adjustment_value_changed (self, adj):
# FIXME: If not visible, disconnect this handler! # FIXME: If not visible, disconnect this handler!
@ -405,9 +480,6 @@ class TimelineFeature (FeatureBase):
if show: if show:
self.timeline.show () self.timeline.show ()
if self.timeline.sentinel is None:
sentinel = LineFrequencySentinel (self.log_model)
self.timeline.set_sentinel (sentinel)
else: else:
self.timeline.hide () self.timeline.hide ()
@ -431,7 +503,7 @@ class TimelineFeature (FeatureBase):
def goto_time_position (self, pos): def goto_time_position (self, pos):
data = self.timeline.sentinel.data data = self.timeline.freq_sentinel.data
if not data: if not data:
return True return True
count = sum (data[:pos + 1]) count = sum (data[:pos + 1])