From d624db18c51ecb50b7ef7905b932272e2c3a70b6 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 3 Dec 2009 12:19:38 -0500 Subject: [PATCH] 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 8bf359d08..5fad513f6 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 5aad6e427..358362432 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 ed24fc373..18ce34b35 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; + }); + } +}