[gnome-shell] Add deferred work system



commit d624db18c51ecb50b7ef7905b932272e2c3a70b6
Author: Colin Walters <walters verbum org>
Date:   Thu Dec 3 12:19:38 2009 -0500

    Add deferred work system
    
    Previously we had various things watching for notify::mapped so
    we could be more lazy about updating non-visible actors, but it
    was fairly ad-hoc.
    
    The deferred work system unifies these callbacks, and also adds
    a timeout so that we don't delay changes arbitrarily; this way we
    avoid a storm of work if you stay out of the overview for a while,
    then go in.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=603522

 js/ui/appDisplay.js   |   41 ++++-------------
 js/ui/lookingGlass.js |    6 +-
 js/ui/main.js         |  117 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 130 insertions(+), 34 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 8bf359d..5fad513 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -236,7 +236,7 @@ BaseWellItem.prototype = {
                                          reactive: true });
         this.actor._delegate = this;
         this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
-        this.actor.connect('notify::mapped', Lang.bind(this, this._onMapped));
+        this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._rerenderGlow));
 
         let box = new St.BoxLayout({ vertical: true });
         this.actor.set_child(box);
@@ -262,8 +262,7 @@ BaseWellItem.prototype = {
         this._glowBox.connect('style-changed', Lang.bind(this, this._onStyleChanged));
         this._nameBox.add_actor(this._glowBox);
         this._glowBox.lower(this._name);
-        this._appWindowChangedId = this.app.connect('windows-changed', Lang.bind(this, this._rerenderGlow));
-        this._rerenderGlow();
+        this._appWindowChangedId = this.app.connect('windows-changed', Lang.bind(this, this._queueRerenderGlow));
 
         box.add(nameBox);
 
@@ -319,18 +318,11 @@ BaseWellItem.prototype = {
             this.app.disconnect(this._appWindowChangedId);
     },
 
-    _onMapped: function() {
-        if (!this._queuedGlowRerender)
-            return;
-        this._queuedGlowRerender = false;
-        this._rerenderGlow();
+    _queueRerenderGlow: function() {
+        Main.queueDeferredWork(this._workId);
     },
 
     _rerenderGlow: function() {
-        if (!this.actor.mapped) {
-            this._queuedGlowRerender = true;
-            return;
-        }
         this._glowBox.destroy_children();
         let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', '');
         let windows = this.app.get_windows();
@@ -956,8 +948,7 @@ AppWell.prototype = {
                                    x_align: Big.BoxAlignment.CENTER });
         this.actor._delegate = this;
 
-        this._pendingRedisplay = false;
-        this.actor.connect('notify::mapped', Lang.bind(this, this._onMappedNotify));
+        this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay));
 
         this._grid = new WellGrid();
         this.actor.append(this._grid.actor, Big.BoxPackFlags.EXPAND);
@@ -965,12 +956,9 @@ AppWell.prototype = {
         this._tracker = Shell.WindowTracker.get_default();
         this._appSystem = Shell.AppSystem.get_default();
 
-        this._appSystem.connect('installed-changed', Lang.bind(this, this._redisplay));
-
-        AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._redisplay));
-        this._tracker.connect('app-running-changed', Lang.bind(this, this._redisplay));
-
-        this._redisplay();
+        this._appSystem.connect('installed-changed', Lang.bind(this, this._queueRedisplay));
+        AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._queueRedisplay));
+        this._tracker.connect('app-running-changed', Lang.bind(this, this._queueRedisplay));
     },
 
     _appIdListToHash: function(apps) {
@@ -980,20 +968,11 @@ AppWell.prototype = {
         return ids;
     },
 
-    _onMappedNotify: function() {
-        let mapped = this.actor.mapped;
-        if (mapped && this._pendingRedisplay)
-            this._redisplay();
+    _queueRedisplay: function () {
+        Main.queueDeferredWork(this._workId);
     },
 
     _redisplay: function () {
-        let mapped = this.actor.mapped;
-        if (!mapped) {
-            this._pendingRedisplay = true;
-            return;
-        }
-        this._pendingRedisplay = false;
-
         this._grid.removeAll();
 
         let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js
index 5aad6e4..3583624 100644
--- a/js/ui/lookingGlass.js
+++ b/js/ui/lookingGlass.js
@@ -318,7 +318,7 @@ ErrorLog.prototype = {
         this.text = new St.Label();
         this.actor.add(this.text);
         this.text.clutter_text.line_wrap = true;
-        this.text.connect('notify::mapped', Lang.bind(this, this._onMappedNotify));
+        this.actor.connect('notify::mapped', Lang.bind(this, this._renderText));
     },
 
     _formatTime: function(d){
@@ -331,8 +331,8 @@ ErrorLog.prototype = {
             + pad(d.getUTCSeconds())+'Z'
     },
 
-    _onMappedNotify: function() {
-        if (!(this.actor.mapped && Main._errorLogStack.length > 0))
+    _renderText: function() {
+        if (!this.actor.mapped)
             return;
         let text = this.text.text;
         let stack = Main._getAndClearErrorStack();
diff --git a/js/ui/main.js b/js/ui/main.js
index ed24fc3..18ce34b 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -425,3 +425,120 @@ function activateWindow(window, time) {
         window.activate(time);
     }
 }
+
+// TODO - replace this timeout with some system to guess when the user might
+// be e.g. just reading the screen and not likely to interact.
+const DEFERRED_TIMEOUT_SECONDS = 20;
+var _deferredWorkData = {};
+// Work scheduled for some point in the future
+var _deferredWorkQueue = [];
+// Work we need to process before the next redraw
+var _beforeRedrawQueue = [];
+// Counter to assign work ids
+var _deferredWorkSequence = 0;
+var _deferredTimeoutId = 0;
+
+function _runDeferredWork(workId) {
+    if (!_deferredWorkData[workId])
+        return;
+    let index = _deferredWorkQueue.indexOf(workId);
+    if (index < 0)
+        return;
+
+    _deferredWorkQueue.splice(index, 1);
+    _deferredWorkData[workId].callback();
+    if (_deferredWorkQueue.length == 0 && _deferredTimeoutId > 0) {
+        Mainloop.source_remove(_deferredTimeoutId);
+        _deferredTimeoutId = 0;
+    }
+}
+
+function _runAllDeferredWork() {
+    while (_deferredWorkQueue.length > 0)
+        _runDeferredWork(_deferredWorkQueue[0]);
+}
+
+function _runBeforeRedrawQueue() {
+    for (let i = 0; i < _beforeRedrawQueue.length; i++) {
+        let workId = _beforeRedrawQueue[i];
+        _runDeferredWork(workId);
+    }
+    _beforeRedrawQueue = [];
+}
+
+function _queueBeforeRedraw(workId) {
+    _beforeRedrawQueue.push(workId);
+    if (_beforeRedrawQueue.length == 1) {
+        Meta.later_add(Meta.LaterType.BEFORE_REDRAW, function () {
+            _runBeforeRedrawQueue();
+            return false;
+        }, null);
+    }
+}
+
+/**
+ * initializeDeferredWork:
+ * @actor: A #ClutterActor
+ * @callback: Function to invoke to perform work
+ *
+ * This function sets up a callback to be invoked when either the
+ * given actor is mapped, or after some period of time when the machine
+ * is idle.  This is useful if your actor isn't always visible on the
+ * screen (for example, all actors in the overview), and you don't want
+ * to consume resources updating if the actor isn't actually going to be
+ * displaying to the user.
+ *
+ * Note that queueDeferredWork is called by default immediately on
+ * initialization as well, under the assumption that new actors
+ * will need it.
+ *
+ * Returns: A string work identifer
+ */
+function initializeDeferredWork(actor, callback, props) {
+    // Turn into a string so we can use as an object property
+    let workId = "" + (++_deferredWorkSequence);
+    _deferredWorkData[workId] = { 'actor': actor,
+                                  'callback': callback };
+    actor.connect('notify::mapped', function () {
+        if (!(actor.mapped && _deferredWorkQueue.indexOf(workId) >= 0))
+            return;
+        _queueBeforeRedraw(workId);
+    });
+    actor.connect('destroy', function() {
+        let index = _deferredWorkQueue.indexOf(workId);
+        if (index >= 0)
+            _deferredWorkQueue.splice(index, 1);
+        delete _deferredWorkData[workId];
+    });
+    queueDeferredWork(workId);
+    return workId;
+}
+
+/**
+ * queueDeferredWork:
+ * @workId: work identifier
+ *
+ * Ensure that the work identified by @workId will be
+ * run on map or timeout.  You should call this function
+ * for example when data being displayed by the actor has
+ * changed.
+ */
+function queueDeferredWork(workId) {
+    let data = _deferredWorkData[workId];
+    if (!data) {
+        global.logError("invalid work id ", workId);
+        return;
+    }
+    if (_deferredWorkQueue.indexOf(workId) < 0)
+        _deferredWorkQueue.push(workId);
+    if (data.actor.mapped) {
+        _queueBeforeRedraw(workId);
+        return;
+    } else if (_deferredTimeoutId == 0) {
+        _deferredTimeoutId = Mainloop.timeout_add_seconds(DEFERRED_TIMEOUT_SECONDS, function () {
+            _runAllDeferredWork();
+            _deferredTimeoutId = 0;
+            return false;
+        });
+    }
+}



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