image_cache.cpp
Go to the documentation of this file.
1 // *****************************************************************************
2 //
3 // Copyright (c) 2014, Southwest Research Institute® (SwRI®)
4 // All rights reserved.
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // * Neither the name of Southwest Research Institute® (SwRI®) nor the
14 // names of its contributors may be used to endorse or promote products
15 // derived from this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 // ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
21 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 //
28 // *****************************************************************************
29 
30 #include <tile_map/image_cache.h>
31 
32 #include <boost/make_shared.hpp>
33 
34 #include <QtAlgorithms>
35 #include <QByteArray>
36 #include <QList>
37 #include <QNetworkAccessManager>
38 #include <QNetworkDiskCache>
39 #include <QUrl>
40 
41 #include <ros/ros.h>
42 
43 namespace tile_map
44 {
45  bool ComparePriority(const ImagePtr left, const ImagePtr right)
46  {
47  return left->Priority() > right->Priority();
48  }
49 
50  const int Image::MAXIMUM_FAILURES = 5;
51 
52  Image::Image(const QString& uri, size_t uri_hash, uint64_t priority) :
53  uri_(uri),
54  uri_hash_(uri_hash),
55  loading_(false),
56  failures_(0),
57  failed_(false),
58  priority_(priority)
59  {
60  }
61 
63  {
64  }
65 
67  {
68  image_ = boost::make_shared<QImage>();
69  }
70 
72  {
73  image_.reset();
74  }
75 
77  {
78  failures_++;
80  }
81 
83 
84  ImageCache::ImageCache(const QString& cache_dir, size_t size) :
85  network_manager_(this),
86  cache_dir_(cache_dir),
87  cache_(size),
88  exit_(false),
89  tick_(0),
90  cache_thread_(new CacheThread(this)),
91  network_request_semaphore_(MAXIMUM_NETWORK_REQUESTS)
92  {
93  QNetworkDiskCache* disk_cache = new QNetworkDiskCache(this);
94  disk_cache->setCacheDirectory(cache_dir_);
95  network_manager_.setCache(disk_cache);
96 
97  connect(&network_manager_, SIGNAL(finished(QNetworkReply*)), this, SLOT(ProcessReply(QNetworkReply*)));
98  connect(cache_thread_, SIGNAL(RequestImage(QString)), this, SLOT(ProcessRequest(QString)));
99 
100  cache_thread_->start();
101  cache_thread_->setPriority(QThread::NormalPriority);
102  }
103 
105  {
106  // After setting our exit flag to true, release any conditions the cache thread
107  // might be waiting on so that it will exit.
108  exit_ = true;
111  cache_thread_->wait();
112  delete cache_thread_;
113  }
114 
116  {
117  cache_.clear();
118  network_manager_.cache()->clear();
119  }
120 
121  ImagePtr ImageCache::GetImage(size_t uri_hash, const QString& uri, int32_t priority)
122  {
123  ImagePtr image;
124 
125  // Retrieve the image reference from the cache, updating the freshness.
126  cache_mutex_.lock();
127 
128  if (failed_.contains(uri_hash))
129  {
130  cache_mutex_.unlock();
131  return image;
132  }
133 
134  ImagePtr* image_ptr = cache_.take(uri_hash);
135  if (!image_ptr)
136  {
137  // If the image is not in the cache, create a new reference.
138  image_ptr = new ImagePtr(boost::make_shared<Image>(uri, uri_hash));
139  image = *image_ptr;
140  if (!cache_.insert(uri_hash, image_ptr))
141  {
142  ROS_ERROR("FAILED TO CREATE HANDLE: %s", uri.toStdString().c_str());
143  image_ptr = 0;
144  }
145  }
146  else
147  {
148  image = *image_ptr;
149 
150  // Add raw pointer back to cache.
151  cache_.insert(uri_hash, image_ptr);
152  }
153 
154  cache_mutex_.unlock();
155 
156  unprocessed_mutex_.lock();
157  if (image && !image->GetImage())
158  {
159  if (!image->Failed())
160  {
161  if (!unprocessed_.contains(uri_hash))
162  {
163  // Set an image's starting priority so that it's higher than the
164  // starting priority of every other image we've requested so
165  // far; that ensures that, all other things being equal, the
166  // most recently requested images will be loaded first.
167  image->SetPriority(priority + tick_++);
168  unprocessed_[uri_hash] = image;
169  uri_to_hash_map_[uri] = uri_hash;
171  }
172  else
173  {
174  // Every time an image is requested but hasn't been loaded yet,
175  // increase its priority. Tiles within the visible area will
176  // be requested more frequently, so this will make them load faster
177  // than tiles the user can't see.
178  image->SetPriority(priority + tick_++);
179  }
180  }
181  else
182  {
183  failed_.insert(uri_hash);
184  }
185  }
186 
187  unprocessed_mutex_.unlock();
188 
189  return image;
190  }
191 
192  void ImageCache::ProcessRequest(QString uri)
193  {
194  QNetworkRequest request;
195  request.setUrl(QUrl(uri));
196  request.setRawHeader("User-Agent", "mapviz-1.0");
197  request.setAttribute(
198  QNetworkRequest::CacheLoadControlAttribute,
199  QNetworkRequest::PreferCache);
200  request.setAttribute(
201  QNetworkRequest::HttpPipeliningAllowedAttribute,
202  true);
203 
204  QNetworkReply *reply = network_manager_.get(request);
205  }
206 
207  void ImageCache::ProcessReply(QNetworkReply* reply)
208  {
209  QString url = reply->url().toString();
210 
211  ImagePtr image;
212  unprocessed_mutex_.lock();
213 
214  size_t hash = uri_to_hash_map_[url];
215  image = unprocessed_[hash];
216  if (image)
217  {
218  if (reply->error() == QNetworkReply::NoError)
219  {
220  QByteArray data = reply->readAll();
221  image->InitializeImage();
222  if (!image->GetImage()->loadFromData(data))
223  {
224  image->ClearImage();
225  image->AddFailure();
226  }
227  }
228  else
229  {
230  ROS_ERROR_THROTTLE(1.0, "NETWORK ERROR: %s", reply->errorString().toStdString().c_str());
231  image->AddFailure();
232  }
233  }
234 
235  unprocessed_.remove(hash);
236  uri_to_hash_map_.remove(url);
237  if (image)
238  {
239  image->SetLoading(false);
240  }
241  network_request_semaphore_.release();
242 
243  unprocessed_mutex_.unlock();
244 
245  reply->deleteLater();
246  }
247 
249 
251  image_cache_(parent),
252  waiting_mutex_()
253  {
254  waiting_mutex_.lock();
255  }
256 
258  {
259  waiting_mutex_.unlock();
260  }
261 
263  {
264  while (!image_cache_->exit_)
265  {
266  // Wait until we're told there are images we need to request.
267  waiting_mutex_.lock();
268 
269  // Next, get all of them and sort them by priority.
271  QList<ImagePtr> images = image_cache_->unprocessed_.values();
273 
274  qSort(images.begin(), images.end(), ComparePriority);
275 
276  // Go through all of them and request them. Qt's network manager will
277  // only handle six simultaneous requests at once, so we use a semaphore
278  // to limit ourselves to that many.
279  // Each individual image will release the semaphore when it is done loading.
280  // Also, only load up to a certain number at a time in this loop. If there
281  // are more left afterward, we'll start over. This ensures that we
282  // concentrate on processing the highest-priority images.
283  int count = 0;
284  while (!image_cache_->exit_ && !images.empty() && count < MAXIMUM_SEQUENTIAL_REQUESTS)
285  {
287 
288  ImagePtr image = images.front();
290  if (!image->Loading() && !image->Failed())
291  {
292  count++;
293  image->SetLoading(true);
294  images.pop_front();
295 
296  QString uri = image->Uri();
297  size_t hash = image_cache_->uri_to_hash_map_[uri];
298  if (uri.startsWith(QString("file:///")))
299  {
300  image->InitializeImage();
301  QString filepath = uri.replace(QString("file:///"), QString("/"));
302  if (!image->GetImage()->load(filepath))
303  {
304  image->ClearImage();
305  image->AddFailure();
306  }
307 
308  image_cache_->unprocessed_.remove(hash);
309  image_cache_->uri_to_hash_map_.remove(uri);
310  image->SetLoading(false);
312  }
313  else
314  {
315  Q_EMIT RequestImage(image->Uri());
316  }
317  }
318  else
319  {
320  images.pop_front();
321  }
323 
324  }
325  if (!images.empty())
326  {
327  waiting_mutex_.unlock();
328  }
329  }
330  }
331 }
QMap< size_t, ImagePtr > unprocessed_
Definition: image_cache.h:121
static const int MAXIMUM_FAILURES
Definition: image_cache.h:96
virtual void run()
void ProcessReply(QNetworkReply *reply)
int32_t failures_
Definition: image_cache.h:90
ImageCache * image_cache_
Definition: image_cache.h:154
QSet< size_t > failed_
Definition: image_cache.h:122
boost::shared_ptr< QImage > image_
Definition: image_cache.h:94
bool ComparePriority(const ImagePtr left, const ImagePtr right)
Definition: image_cache.cpp:45
QNetworkAccessManager network_manager_
Definition: image_cache.h:116
data
void InitializeImage()
Definition: image_cache.cpp:66
QSemaphore network_request_semaphore_
Definition: image_cache.h:133
QCache< size_t, ImagePtr > cache_
Definition: image_cache.h:120
Image(const QString &uri, size_t uri_hash, uint64_t priority=0)
Definition: image_cache.cpp:52
#define ROS_ERROR_THROTTLE(rate,...)
ImageCache(const QString &cache_dir, size_t size=4096)
Definition: image_cache.cpp:84
boost::shared_ptr< Image > ImagePtr
Definition: image_cache.h:98
ImagePtr GetImage(size_t uri_hash, const QString &uri, int32_t priority=0)
static const int MAXIMUM_NETWORK_REQUESTS
Definition: image_cache.h:137
CacheThread(ImageCache *parent)
void RequestImage(QString)
QMap< QString, size_t > uri_to_hash_map_
Definition: image_cache.h:123
void ProcessRequest(QString uri)
#define ROS_ERROR(...)
static const int MAXIMUM_SEQUENTIAL_REQUESTS
Definition: image_cache.h:157
CacheThread * cache_thread_
Definition: image_cache.h:131


tile_map
Author(s): Marc Alban
autogenerated on Fri Mar 19 2021 02:44:47