~smspillaz/unity/untiy.less-paint-insanity

« back to all changes in this revision

Viewing changes to plugins/unityshell/src/IconLoader.cpp

  • Committer: Daniel van Vugt
  • Date: 2012-03-14 06:24:18 UTC
  • mfrom: (2108 unity)
  • mto: This revision was merged to the branch mainline in revision 2146.
  • Revision ID: daniel.van.vugt@canonical.com-20120314062418-nprucpbr0m7qky5e
MergedĀ latestĀ lp:unity

Show diffs side-by-side

added added

removed removed

Lines of Context:
76
76
    REQUEST_TYPE_URI,
77
77
  };
78
78
 
 
79
  struct IconLoaderTask;
 
80
  typedef std::list<IconLoaderTask*> TaskList;
 
81
 
79
82
  struct IconLoaderTask
80
83
  {
81
84
    IconLoaderRequestType type;
85
88
    IconLoaderCallback    slot;
86
89
    Handle                handle;
87
90
    Impl*                 self;
 
91
    GtkIconInfo*          icon_info;
 
92
    GdkPixbuf*            result;
 
93
    glib::Error           error;
 
94
    TaskList              shadow_tasks;
88
95
 
89
96
    IconLoaderTask(IconLoaderRequestType type_,
90
97
                   std::string const& data_,
95
102
                   Impl* self_)
96
103
      : type(type_), data(data_), size(size_), key(key_)
97
104
      , slot(slot_), handle(handle_), self(self_)
 
105
      , icon_info(NULL), result(NULL)
98
106
      {}
 
107
 
 
108
    void InvokeSlot(GdkPixbuf* pixbuf)
 
109
    {
 
110
      slot(data, size, pixbuf);
 
111
 
 
112
      // notify shadow tasks
 
113
      for (auto shadow_task : shadow_tasks)
 
114
      {
 
115
        shadow_task->slot(shadow_task->data, shadow_task->size, pixbuf);
 
116
 
 
117
        self->task_map_.erase(shadow_task->handle);
 
118
        delete shadow_task;
 
119
      }
 
120
 
 
121
      shadow_tasks.clear();
 
122
    }
99
123
  };
100
124
 
101
125
  Handle ReturnCachedOrQueue(std::string const& data,
116
140
                   unsigned size,
117
141
                   IconLoaderCallback slot);
118
142
 
 
143
  // these methods might run asynchronously
119
144
  bool ProcessTask(IconLoaderTask* task);
120
145
  bool ProcessIconNameTask(IconLoaderTask* task);
121
146
  bool ProcessGIconTask(IconLoaderTask* task);
122
 
 
123
 
  // URI processing is async.
124
147
  bool ProcessURITask(IconLoaderTask* task);
125
 
  void ProcessURITaskReady(IconLoaderTask* task, char* contents, gsize length);
126
 
  static void LoadContentsReady(GObject* object, GAsyncResult* res, IconLoaderTask* task);
 
148
 
 
149
  // Loading/rendering of pixbufs is done in a separate thread
 
150
  static gboolean LoaderJobFunc(GIOSchedulerJob* job, GCancellable *canc,
 
151
                                IconLoaderTask *task);
 
152
  static gboolean LoadIconComplete(IconLoaderTask* task);
 
153
  static gboolean CoalesceTasksCb(IconLoader::Impl* self);
127
154
 
128
155
  // Loop calls the iteration function.
129
 
  static bool Loop(Impl* self);
 
156
  static gboolean Loop(Impl* self);
130
157
  bool Iteration();
131
158
 
132
159
private:
133
160
  typedef std::map<std::string, glib::Object<GdkPixbuf>> ImageCache;
134
161
  ImageCache cache_;
 
162
  typedef std::map<std::string, IconLoaderTask*> CacheQueue;
 
163
  CacheQueue queued_tasks_;
135
164
  typedef std::queue<IconLoaderTask*> TaskQueue;
136
165
  TaskQueue tasks_;
137
166
  typedef std::map<Handle, IconLoaderTask*> TaskMap;
138
167
  TaskMap task_map_;
 
168
  typedef std::vector<IconLoaderTask*> TaskArray;
 
169
  TaskArray finished_tasks_;
139
170
 
140
171
  guint idle_id_;
 
172
  guint coalesce_id_;
141
173
  bool no_load_;
142
174
  GtkIconTheme* theme_; // Not owned.
143
175
  Handle handle_counter_;
146
178
 
147
179
IconLoader::Impl::Impl()
148
180
  : idle_id_(0)
149
 
  // Option to disable loading, if your testing performance of other things
 
181
  , coalesce_id_(0)
 
182
  // Option to disable loading, if you're testing performance of other things
150
183
  , no_load_(::getenv("UNITY_ICON_LOADER_DISABLE"))
151
184
  , theme_(::gtk_icon_theme_get_default())
152
185
  , handle_counter_(0)
249
282
{
250
283
  IconLoaderTask* task = new IconLoaderTask(type, data, size, key,
251
284
                                            slot, ++handle_counter_, this);
 
285
 
 
286
  auto iter = queued_tasks_.find(key);
 
287
  bool already_queued = iter != queued_tasks_.end();
 
288
  IconLoaderTask* running_task = already_queued ? iter->second : NULL;
 
289
 
 
290
  if (running_task != NULL)
 
291
  {
 
292
    running_task->shadow_tasks.push_back(task);
 
293
    // do NOT push the task into the tasks queue,
 
294
    // the parent task (which is in the queue) will handle it
 
295
    task_map_[task->handle] = task;
 
296
 
 
297
    LOG_DEBUG(logger) << "Appending shadow task  " << data
 
298
                      << ", queue size now at " << tasks_.size();
 
299
 
 
300
    return task->handle;
 
301
  }
 
302
  else
 
303
  {
 
304
    queued_tasks_[key] = task;
 
305
  }
 
306
 
252
307
  tasks_.push(task);
253
308
  task_map_[task->handle] = task;
254
309
 
306
361
  LOG_WARNING(logger) << "Request type " << task->type
307
362
                      << " is not supported (" << task->data
308
363
                      << " " << task->size << ")";
309
 
  task->slot(task->data, task->size, nullptr);
 
364
  task->InvokeSlot(nullptr);
310
365
  return true;
311
366
}
312
367
 
313
368
bool IconLoader::Impl::ProcessIconNameTask(IconLoaderTask* task)
314
369
{
315
 
  GdkPixbuf* pixbuf = nullptr;
316
370
  GtkIconInfo* info = gtk_icon_theme_lookup_icon(theme_,
317
371
                                                 task->data.c_str(),
318
372
                                                 task->size,
319
373
                                                 (GtkIconLookupFlags)0);
320
374
  if (info)
321
375
  {
322
 
    glib::Error error;
 
376
    task->icon_info = info;
 
377
    g_io_scheduler_push_job ((GIOSchedulerJobFunc) LoaderJobFunc,
 
378
                             task, NULL, G_PRIORITY_HIGH_IDLE, NULL);
323
379
 
324
 
    pixbuf = gtk_icon_info_load_icon(info, &error);
325
 
    if (GDK_IS_PIXBUF(pixbuf))
326
 
    {
327
 
      cache_[task->key] = pixbuf;
328
 
    }
329
 
    else
330
 
    {
331
 
      LOG_WARNING(logger) << "Unable to load icon " << task->data
332
 
                          << " at size " << task->size << ": " << error;
333
 
    }
334
 
    gtk_icon_info_free(info);
 
380
    return false;
335
381
  }
336
382
  else
337
383
  {
339
385
                        << " at size " << task->size;
340
386
  }
341
387
 
342
 
  task->slot(task->data, task->size, pixbuf);
 
388
  task->InvokeSlot(nullptr);
343
389
  return true;
344
390
}
345
391
 
346
392
bool IconLoader::Impl::ProcessGIconTask(IconLoaderTask* task)
347
393
{
348
 
  GdkPixbuf*   pixbuf = NULL;
349
 
 
350
394
  glib::Error error;
351
395
  glib::Object<GIcon> icon(::g_icon_new_for_string(task->data.c_str(), &error));
352
396
 
368
412
                                                         (GtkIconLookupFlags)0);
369
413
    if (info)
370
414
    {
371
 
      pixbuf = gtk_icon_info_load_icon(info, &error);
 
415
      task->icon_info = info;
 
416
      g_io_scheduler_push_job ((GIOSchedulerJobFunc) LoaderJobFunc,
 
417
                               task, NULL, G_PRIORITY_HIGH_IDLE, NULL);
372
418
 
373
 
      if (GDK_IS_PIXBUF(pixbuf))
374
 
      {
375
 
        cache_[task->key] = pixbuf;
376
 
      }
377
 
      else
378
 
      {
379
 
        LOG_WARNING(logger) << "Unable to load icon " << task->data
380
 
                            << " at size " << task->size << ": " << error;
381
 
      }
382
 
      gtk_icon_info_free(info);
 
419
      return false;
383
420
    }
384
421
    else
385
422
    {
409
446
                        << " at size " << task->size << ": " << error;
410
447
  }
411
448
 
412
 
  task->slot(task->data, task->size, pixbuf);
 
449
  task->InvokeSlot(nullptr);
413
450
  return true;
414
451
}
415
452
 
416
453
bool IconLoader::Impl::ProcessURITask(IconLoaderTask* task)
417
454
{
418
 
  glib::Object<GFile> file(g_file_new_for_uri(task->data.c_str()));
419
 
 
420
 
  g_file_load_contents_async(file,
421
 
                             NULL,
422
 
                             (GAsyncReadyCallback)LoadContentsReady,
423
 
                             task);
 
455
  g_io_scheduler_push_job ((GIOSchedulerJobFunc) LoaderJobFunc,
 
456
                           task, NULL, G_PRIORITY_HIGH_IDLE, NULL);
424
457
 
425
458
  return false;
426
459
}
427
460
 
428
 
void IconLoader::Impl::ProcessURITaskReady(IconLoaderTask* task,
429
 
                                           char* contents,
430
 
                                           gsize length)
431
 
{
432
 
  GInputStream* stream = g_memory_input_stream_new_from_data(contents, length, NULL);
433
 
 
434
 
  glib::Error error;
435
 
  glib::Object<GdkPixbuf> pixbuf(gdk_pixbuf_new_from_stream_at_scale(stream,
436
 
                                                                     -1,
437
 
                                                                     task->size,
438
 
                                                                     true,
439
 
                                                                     NULL,
440
 
                                                                     &error));
441
 
  if (error)
442
 
  {
443
 
    LOG_WARNING(logger) << "Unable to create pixbuf from input stream for "
444
 
                        << task->data << " at size " << task->size << ": " << error;
445
 
  }
446
 
  else
447
 
  {
448
 
    cache_[task->key] = pixbuf;
449
 
  }
450
 
 
451
 
  task->slot(task->data, task->size, pixbuf);
452
 
  g_input_stream_close(stream, NULL, NULL);
 
461
gboolean IconLoader::Impl::LoaderJobFunc(GIOSchedulerJob* job,
 
462
                                         GCancellable *canc,
 
463
                                         IconLoaderTask *task)
 
464
{
 
465
  // careful here this is running in non-main thread
 
466
  if (task->icon_info)
 
467
  {
 
468
    task->result = gtk_icon_info_load_icon(task->icon_info, &task->error);
 
469
 
 
470
    gtk_icon_info_free (task->icon_info);
 
471
    task->icon_info = NULL;
 
472
  }
 
473
  else if (task->type == REQUEST_TYPE_URI)
 
474
  {
 
475
    glib::Object<GFile> file(g_file_new_for_uri(task->data.c_str()));
 
476
    glib::String contents;
 
477
    gsize length = 0;
 
478
 
 
479
    if (g_file_load_contents(file, canc, &contents, &length,
 
480
                             NULL, &task->error))
 
481
    {
 
482
      glib::Object<GInputStream> stream(
 
483
          g_memory_input_stream_new_from_data(contents.Value(), length, NULL));
 
484
 
 
485
      task->result = gdk_pixbuf_new_from_stream_at_scale(stream,
 
486
                                                         -1,
 
487
                                                         task->size,
 
488
                                                         TRUE,
 
489
                                                         canc,
 
490
                                                         &task->error);
 
491
      g_input_stream_close(stream, canc, NULL);
 
492
    }
 
493
  }
 
494
 
 
495
  g_io_scheduler_job_send_to_mainloop_async (job,
 
496
                                             (GSourceFunc) LoadIconComplete,
 
497
                                             task,
 
498
                                             NULL);
 
499
 
 
500
  return FALSE;
 
501
}
 
502
 
 
503
// this will be invoked back in the thread from which push_job was called
 
504
gboolean IconLoader::Impl::LoadIconComplete(IconLoaderTask* task)
 
505
{
 
506
  if (task->self->coalesce_id_ == 0)
 
507
  {
 
508
    // we're using lower priority than the GIOSchedulerJob uses to deliver
 
509
    // results to the mainloop
 
510
    task->self->coalesce_id_ =
 
511
      g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE + 10,
 
512
                          40,
 
513
                          (GSourceFunc) IconLoader::Impl::CoalesceTasksCb,
 
514
                          task->self,
 
515
                          NULL);
 
516
  }
 
517
 
 
518
  task->self->finished_tasks_.push_back (task);
 
519
 
 
520
  return FALSE;
 
521
}
 
522
 
 
523
gboolean IconLoader::Impl::CoalesceTasksCb(IconLoader::Impl* self)
 
524
{
 
525
  for (auto task : self->finished_tasks_)
 
526
  {
 
527
    // FIXME: we could update the cache sooner, but there are ref-counting
 
528
    // issues on the pixbuf (and inside the slot callbacks) that prevent us
 
529
    // from doing that.
 
530
    if (GDK_IS_PIXBUF(task->result))
 
531
    {
 
532
      task->self->cache_[task->key] = task->result;
 
533
    }
 
534
    else
 
535
    {
 
536
      LOG_WARNING(logger) << "Unable to load icon " << task->data
 
537
                          << " at size " << task->size << ": " << task->error;
 
538
    }
 
539
 
 
540
    task->InvokeSlot(task->result);
 
541
 
 
542
    // this was all async, we need to erase the task from the task_map
 
543
    self->task_map_.erase(task->handle);
 
544
    self->queued_tasks_.erase(task->key);
 
545
    delete task;
 
546
  }
 
547
 
 
548
  self->finished_tasks_.clear ();
 
549
  self->coalesce_id_ = 0;
 
550
 
 
551
  return FALSE;
453
552
}
454
553
 
455
554
bool IconLoader::Impl::Iteration()
456
555
{
457
 
  static const int MAX_MICRO_SECS = 10000;
 
556
  static const int MAX_MICRO_SECS = 1000;
458
557
  util::Timer timer;
459
558
 
460
559
  bool queue_empty = tasks_.empty();
461
560
 
462
 
  while (!queue_empty &&
463
 
         (timer.ElapsedMicroSeconds() < MAX_MICRO_SECS))
 
561
  // always do at least one iteration if the queue isn't empty
 
562
  while (!queue_empty)
464
563
  {
465
564
    IconLoaderTask* task = tasks_.front();
466
565
 
467
566
    if (ProcessTask(task))
468
567
    {
469
568
      task_map_.erase(task->handle);
 
569
      queued_tasks_.erase(task->key);
470
570
      delete task;
471
571
    }
472
572
 
473
573
    tasks_.pop();
474
574
    queue_empty = tasks_.empty();
 
575
 
 
576
    if (timer.ElapsedMicroSeconds() >= MAX_MICRO_SECS) break;
475
577
  }
476
578
 
477
579
  LOG_DEBUG(logger) << "Iteration done, queue size now at " << tasks_.size();
487
589
}
488
590
 
489
591
 
490
 
bool IconLoader::Impl::Loop(IconLoader::Impl* self)
491
 
{
492
 
  return self->Iteration();
493
 
}
494
 
 
495
 
void IconLoader::Impl::LoadContentsReady(GObject* obj,
496
 
                                         GAsyncResult* res,
497
 
                                         IconLoaderTask* task)
498
 
{
499
 
  glib::String contents;
500
 
  glib::Error error;
501
 
  gsize length = 0;
502
 
 
503
 
  if (g_file_load_contents_finish(G_FILE(obj), res, &contents, &length, NULL, &error))
504
 
  {
505
 
    task->self->ProcessURITaskReady(task, contents.Value(), length);
506
 
  }
507
 
  else
508
 
  {
509
 
    LOG_WARNING(logger) << "Unable to load contents of "
510
 
                        << task->data << ": " << error;
511
 
    task->slot(task->data, task->size, nullptr);
512
 
  }
513
 
  task->self->task_map_.erase(task->handle);
514
 
  delete task;
515
 
}
516
 
 
 
592
gboolean IconLoader::Impl::Loop(IconLoader::Impl* self)
 
593
{
 
594
  return self->Iteration() ? TRUE : FALSE;
 
595
}
517
596
 
518
597
IconLoader::IconLoader()
519
598
  : pimpl(new Impl())