Merge lp:~unity-team/unity/fast-icons into lp:unity

Proposed by Mikkel Kamstrup Erlandsen
Status: Merged
Approved by: Neil J. Patel
Approved revision: no longer in the source branch.
Merged at revision: 535
Proposed branch: lp:~unity-team/unity/fast-icons
Merge into: lp:unity
Diff against target: 499 lines (+209/-162)
3 files modified
unity-private/places/places-default-renderer-group.vala (+0/-3)
unity/unity-io.vala (+1/-1)
unity/unity-pixbuf-cache.vala (+208/-158)
To merge this branch: bzr merge lp:~unity-team/unity/fast-icons
Reviewer Review Type Date Requested Status
Neil J. Patel Pending
Review via email: mp+36272@code.launchpad.net

Description of the change

This branch optimizes the icon loading considerably. Most notably the places will now appear on screen much faster. You will note that on initial launch of a place the icons will not appear instantly - in fact may appear to arrive slower than before. This is due to the fact that the new code shows the place on screen much earlier than the old code.

It also fixes the bug where tiles in the places could sometimes have the wrong icon.

DISCLAIMER: It breaks ABI and API of Unity.IO. Slightly. This was required to remove a heckuwa lot of reallocations of some big IO buffers. Unity internally has been updated to reflect this (of course). The place daemons are the only other consumers, and I will update them to require the latest unity, which should make this a non-problem.

To post a comment you must log in.
Revision history for this message
Neil J. Patel (njpatel) wrote :

Works very well and I think teh ABI break is fine as libunity is still only used within the DX team. Approved!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'unity-private/places/places-default-renderer-group.vala'
2--- unity-private/places/places-default-renderer-group.vala 2010-09-15 14:42:59 +0000
3+++ unity-private/places/places-default-renderer-group.vala 2010-09-22 10:01:04 +0000
4@@ -426,9 +426,6 @@
5 more_results_button.count = 0;
6 }
7 }
8-
9- if (n_results == 1)
10- PixbufCache.get_default ().load_iteration ();
11 }
12
13 private void on_n_cols_changed ()
14
15=== modified file 'unity/unity-io.vala'
16--- unity/unity-io.vala 2010-06-28 12:56:11 +0000
17+++ unity/unity-io.vala 2010-09-22 10:01:04 +0000
18@@ -41,7 +41,7 @@
19 * Important: The passed back data must be manually freed using g_free()
20 */
21 public async void read_stream_async (InputStream input,
22- owned uchar[] buffer,
23+ void* buffer,
24 size_t buffer_lenght,
25 int io_priority,
26 Cancellable? cancellable,
27
28=== modified file 'unity/unity-pixbuf-cache.vala'
29--- unity/unity-pixbuf-cache.vala 2010-09-09 14:16:53 +0000
30+++ unity/unity-pixbuf-cache.vala 2010-09-22 10:01:04 +0000
31@@ -23,7 +23,7 @@
32
33 namespace Unity
34 {
35- public class PixbufCacheTask
36+ private class PixbufCacheTask
37 {
38 public string data;
39 public unowned Ctk.Image image;
40@@ -36,7 +36,7 @@
41 }
42 }
43
44- public enum PixbufRequestType
45+ private enum PixbufRequestType
46 {
47 ICON_NAME,
48 GICON_STRING
49@@ -62,9 +62,9 @@
50
51 public uint size { get { return cache.size; } }
52
53- private PriorityQueue<PixbufCacheTask> queue;
54+ private Gee.Queue<PixbufCacheTask> queue;
55
56- private uint queue_timeout = 0;
57+ private uint queue_source = 0;
58
59 /*
60 * Construction
61@@ -83,14 +83,13 @@
62
63 construct
64 {
65- queue = new PriorityQueue<PixbufCacheTask> ();
66-
67+ queue = new LinkedList<PixbufCacheTask> ();
68 theme = Gtk.IconTheme.get_default ();
69 cache = new HashMap<string, Gdk.Pixbuf> ();
70 }
71
72 private void on_shell_destroyed ()
73- {
74+ {
75 if (_pixbuf_cache == this)
76 {
77 _pixbuf_cache = null;
78@@ -127,31 +126,79 @@
79 cache.clear ();
80 }
81
82- public bool load_iteration ()
83+ private async void process_icon_queue_async ()
84 {
85- int i = 0;
86-
87- while (queue.size > 0 && i < 10)
88+ /* Hardcoded numbers make gord cry - but so be it - we are taking
89+ * a second guess of the inode size here, for the better of mankind */
90+ uchar[] buf = new uchar[4096];
91+ int count = 0;
92+ var timer = new Timer ();
93+ PixbufCacheTask task;
94+
95+ while ((task = queue.poll()) != null)
96 {
97- var task = queue.poll ();
98-
99 if (task.image is Ctk.Image)
100 {
101- if (task.type == PixbufRequestType.ICON_NAME)
102- set_image_from_icon_name_real (task.image, task.data, task.size);
103- else if (task.type == PixbufRequestType.GICON_STRING)
104- set_image_from_gicon_string_real (task.image, task.data, task.size);
105- }
106-
107- i++;
108- }
109-
110- if (queue.size == 0)
111- queue_timeout = 0;
112-
113- return queue.size != 0;
114- }
115-
116+ switch (task.type)
117+ {
118+ case PixbufRequestType.ICON_NAME:
119+ yield set_image_from_icon_name_real (task, buf, buf.length);
120+ break;
121+ case PixbufRequestType.GICON_STRING:
122+ yield set_image_from_gicon_string_real (task, buf, buf.length);
123+ break;
124+ default:
125+ critical ("Internal error. Unknown PixbufRequestType: %u",
126+ task.type);
127+ break;
128+ }
129+ }
130+ count++;
131+ }
132+
133+ timer.stop ();
134+ debug ("Loaded %i icons in %fms", count, timer.elapsed()*1000);
135+
136+ /* Queue depleted */
137+ queue_source = 0;
138+ }
139+
140+ private void process_icon_queue ()
141+ {
142+ if (queue_source == 0)
143+ {
144+ /* queue_source is set to 0 by process_icon_queue_async()
145+ * when the queue is depleted.
146+ * It's important that we dispatch in an idle call here since
147+ * it gives clients a chance to queue more icons before we
148+ * hammer the IO */
149+ queue_source = Idle.add (dispatch_process_icon_queue_async);
150+ }
151+ }
152+
153+ private bool dispatch_process_icon_queue_async ()
154+ {
155+ /* Run three processings in "parallel". While we are not multithreaded,
156+ * it still makes sense to do since one branch may be waiting in
157+ * 'yield' for async IO, while the other can be parsing image data.
158+ * The number of parallel workers has been chosen by best perceived
159+ * responsiveness for bringing up the Applications place */
160+ process_icon_queue_async.begin ();
161+ process_icon_queue_async.begin ();
162+ process_icon_queue_async.begin ();
163+
164+ return false;
165+ }
166+
167+ /**
168+ * If the icon is already cached then set it immediately on @image. Otherwise
169+ * does async IO to load and cache the icon, then setting it on @image.
170+ *
171+ * Note that this means that you should treat this method as an async
172+ * operation for all intents and purposes. You can not count on
173+ * image.pixbuf != null when this method call returns. That may just as well
174+ * happen in a subsequent idle call.
175+ */
176 public void set_image_from_icon_name (Ctk.Image image,
177 string icon_name,
178 int size)
179@@ -166,161 +213,165 @@
180 }
181
182 var task = new PixbufCacheTask ();
183+ task.key = key;
184 task.data = icon_name;
185 task.image = image;
186 task.size = size;
187 task.type = PixbufRequestType.ICON_NAME;
188
189- queue.add (task);
190-
191- if (queue_timeout == 0)
192- queue_timeout = Idle.add (load_iteration);
193- }
194-
195- private async void set_image_from_icon_name_real (Ctk.Image image,
196- string icon_name,
197- int size)
198- {
199- var key = hash_template.printf (icon_name, size);
200+ queue.offer (task);
201+
202+ process_icon_queue ();
203+ }
204+
205+ private async void set_image_from_icon_name_real (PixbufCacheTask task,
206+ void* buf,
207+ size_t buf_length)
208+ {
209+ Pixbuf? ret = cache[task.key];
210+
211+ /* We need a secondary cache check because the icon may have
212+ * been cached while we where waiting in the queue */
213+ if (ret is Pixbuf)
214+ {
215+ task.image.set_from_pixbuf (ret);
216+ return;
217+ }
218+
219+ try {
220+ var info = theme.lookup_icon (task.data, task.size, 0);
221+ if (info != null)
222+ {
223+ var filename = info.get_filename ();
224+ ret = yield load_from_filepath (filename, task.size, buf, buf_length);
225+ }
226+
227+ if (ret is Pixbuf)
228+ {
229+ cache[task.key] = ret;
230+ }
231+
232+ } catch (Error e) {
233+ warning ("Unable to load icon_name: %s", e.message);
234+ }
235+
236+ if (ret is Pixbuf)
237+ {
238+ task.image.set_from_pixbuf (ret);
239+ }
240+ }
241+
242+ /**
243+ * If the icon is already cached then set it immediately on @image. Otherwise
244+ * does async IO to load and cache the icon, then setting it on @image.
245+ *
246+ * Note that this means that you should treat this method as an async
247+ * operation for all intents and purposes. You can not count on
248+ * image.pixbuf != null when this method call returns. That may just as well
249+ * happen in a subsequent idle call.
250+ */
251+ public void set_image_from_gicon_string (Ctk.Image image,
252+ string gicon_as_string,
253+ int size)
254+ {
255+ var key = hash_template.printf (gicon_as_string, size);
256 Pixbuf? ret = cache[key];
257
258- /* We need a secondary cache check because the icon may have
259- * been cached while we where waiting in the queue */
260 if (ret is Pixbuf)
261 {
262 image.set_from_pixbuf (ret);
263 return;
264 }
265+
266+ var task = new PixbufCacheTask ();
267+ task.key = key;
268+ task.image = image;
269+ task.data = gicon_as_string;
270+ task.size = size;
271+ task.type = PixbufRequestType.GICON_STRING;
272+
273+ queue.offer (task);
274+
275+ process_icon_queue ();
276+ }
277+
278+ private async void set_image_from_gicon_string_real (PixbufCacheTask task,
279+ void* buf,
280+ size_t buf_length)
281+ {
282+ Pixbuf? ret = cache[task.key];
283+
284+ /* We need a secondary cache check because the icon may have
285+ * been cached while we where waiting in the queue */
286+ if (ret is Pixbuf)
287+ {
288+ task.image.set_from_pixbuf (ret);
289+ return;
290+ }
291
292+ if (task.data[0] == '/')
293+ {
294+ try {
295+ ret = yield load_from_filepath (task.data, task.size, buf, buf_length);
296+ } catch (Error err) {
297+ message (@"Unable to load $(task.data) as file: %s",
298+ err.message);
299+ }
300+ }
301+
302 if (ret == null)
303 {
304 try {
305- var info = theme.lookup_icon (icon_name, size, 0);
306+ unowned GLib.Icon icon = GLib.Icon.new_for_string (task.data);
307+ var info = theme.lookup_by_gicon (icon, task.size, 0);
308 if (info != null)
309 {
310 var filename = info.get_filename ();
311- ret = yield load_from_filepath (filename, size);
312+ ret = yield load_from_filepath (filename, task.size, buf, buf_length);
313 }
314
315- if (ret is Pixbuf)
316+ if (ret == null)
317 {
318- cache[key] = ret;
319+ /* There is some funkiness in some programs where they install
320+ * their icon to /usr/share/icons/hicolor/apps/, but they
321+ * name the Icon= key as `foo.$extension` which breaks loading
322+ * So we can try and work around that here.
323+ */
324+ if (task.data.has_suffix (".png")
325+ || task.data.has_suffix (".xpm")
326+ || task.data.has_suffix (".gif")
327+ || task.data.has_suffix (".jpg"))
328+ {
329+ string real_name = task.data[0:task.data.length-4];
330+ info = theme.lookup_icon (real_name, task.size, 0);
331+ if (info != null)
332+ {
333+ var fname = info.get_filename ();
334+ ret = yield load_from_filepath (fname, task.size, buf, buf_length);
335+ }
336+ }
337 }
338-
339 } catch (Error e) {
340- warning ("Unable to load icon_name: %s", e.message);
341+ warning (@"Unable to load icon $(task.data): '%s'", e.message);
342 }
343 }
344
345 if (ret is Pixbuf)
346 {
347- image.set_from_pixbuf (ret);
348- }
349- }
350-
351- public void set_image_from_gicon_string (Ctk.Image image,
352- string data,
353- int size)
354- {
355- var key = hash_template.printf (data, size);
356- Pixbuf? ret = cache[key];
357-
358- if (ret is Pixbuf)
359- {
360- image.set_from_pixbuf (ret);
361- return;
362- }
363-
364- var task = new PixbufCacheTask ();
365- task.image = image;
366- task.data = data;
367- task.size = size;
368- task.type = PixbufRequestType.GICON_STRING;
369-
370- queue.add (task);
371-
372- if (queue_timeout == 0)
373- queue_timeout = Idle.add (load_iteration);
374- }
375-
376- private async void set_image_from_gicon_string_real (Ctk.Image image,
377- string gicon_as_string,
378- int size)
379- {
380- var key = hash_template.printf (gicon_as_string, size);
381- Pixbuf? ret = cache[key];
382-
383- /* We need a secondary cache check because the icon may have
384- * been cached while we where waiting in the queue */
385- if (ret is Pixbuf)
386- {
387- image.set_from_pixbuf (ret);
388- return;
389- }
390-
391- if (ret == null)
392- {
393- if (gicon_as_string[0] == '/')
394- {
395- try {
396- ret = yield load_from_filepath (gicon_as_string, size);
397- } catch (Error err) {
398- message (@"Unable to load $gicon_as_string as file: %s",
399- err.message);
400- }
401- }
402-
403- if (ret == null)
404- {
405- try {
406- unowned GLib.Icon icon = GLib.Icon.new_for_string (gicon_as_string);
407- var info = theme.lookup_by_gicon (icon, size, 0);
408- if (info != null)
409- {
410- var filename = info.get_filename ();
411-
412- ret = yield load_from_filepath (filename, size);
413- }
414-
415- if (ret == null)
416- {
417- /* There is some funkiness in some programs where they install
418- * their icon to /usr/share/icons/hicolor/apps/, but they
419- * name the Icon= key as `foo.$extension` which breaks loading
420- * So we can try and work around that here.
421- */
422- if (gicon_as_string.has_suffix (".png")
423- || gicon_as_string.has_suffix (".xpm")
424- || gicon_as_string.has_suffix (".gif")
425- || gicon_as_string.has_suffix (".jpg"))
426- {
427- string real_name = gicon_as_string[0:gicon_as_string.length-4];
428- info = theme.lookup_icon (real_name, size, 0);
429- if (info != null)
430- {
431- var fname = info.get_filename ();
432- ret = yield load_from_filepath (fname, size);
433- }
434- }
435- }
436-
437- } catch (Error e) {
438- warning (@"Unable to load icon $gicon_as_string: '%s'", e.message);
439- }
440- }
441-
442- if (ret is Pixbuf)
443- {
444- cache[key] = ret;
445- }
446- }
447-
448- if (ret is Pixbuf)
449- {
450- image.set_from_pixbuf (ret);
451- }
452- }
453-
454+ cache[task.key] = ret;
455+ task.image.set_from_pixbuf (ret);
456+ }
457+ }
458+
459+ /**
460+ * If @icon is already cached then set it immediately on @image. Otherwise
461+ * does async IO to load and cache the icon, then setting it on @image.
462+ *
463+ * Note that this means that you should treat this method as an async
464+ * operation for all intents and purposes. You can not count on
465+ * image.pixbuf != null when this method call returns. That may just as well
466+ * happen in a subsequent idle call.
467+ */
468 public async void set_image_from_gicon (Ctk.Image image,
469 GLib.Icon icon,
470 int size)
471@@ -328,7 +379,8 @@
472 set_image_from_gicon_string (image, icon.to_string (), size);
473 }
474
475- public async Gdk.Pixbuf? load_from_filepath (string filename, int size)
476+ private async Gdk.Pixbuf? load_from_filepath (string filename, int size,
477+ void* buf, size_t buf_length) throws Error
478 {
479
480 if (filename != null)
481@@ -347,10 +399,9 @@
482 if (stream is FileInputStream)
483 {
484 /* TODO: Tweak buf size to optimize io */
485- uchar[] buf = new uchar[1024];
486 void *data;
487 size_t data_size;
488- yield IO.read_stream_async (stream, buf, buf.length, Priority.DEFAULT,
489+ yield IO.read_stream_async (stream, buf, buf_length, Priority.DEFAULT,
490 null, out data, out data_size);
491
492 /* Construct a loader for a given mimetype so it doesn't
493@@ -374,6 +425,5 @@
494
495 return null;
496 }
497-
498 }
499 }