comparison base/MatrixFileCache.cpp @ 95:040a151d0897

* Add file reader thread, and make the matrix file code use it to preload fft cache data without glitching
author Chris Cannam
date Thu, 04 May 2006 13:59:57 +0000
parents 27d726916ab3
children
comparison
equal deleted inserted replaced
94:5b8392e80ed6 95:040a151d0897
25 #include <iostream> 25 #include <iostream>
26 26
27 #include <cstdio> 27 #include <cstdio>
28 28
29 #include <QFileInfo> 29 #include <QFileInfo>
30 #include <QFile>
31 #include <QDir> 30 #include <QDir>
32 31
33 std::map<QString, int> MatrixFileCache::m_refcount; 32 std::map<QString, int> MatrixFileCache::m_refcount;
34 QMutex MatrixFileCache::m_refcountMutex; 33 QMutex MatrixFileCache::m_refcountMutex;
35
36 //!!! This class is a work in progress -- it does only as much as we
37 // need for the current SpectrogramLayer. Slated for substantial
38 // refactoring and extension.
39 34
40 MatrixFileCache::MatrixFileCache(QString fileBase, Mode mode) : 35 MatrixFileCache::MatrixFileCache(QString fileBase, Mode mode) :
41 m_fd(-1), 36 m_fd(-1),
42 m_mode(mode), 37 m_mode(mode),
43 m_width(0), 38 m_width(0),
44 m_height(0), 39 m_height(0),
45 m_headerSize(2 * sizeof(size_t)), 40 m_headerSize(2 * sizeof(size_t)),
46 m_autoRegionWidth(256), 41 m_defaultCacheWidth(2048),
47 m_off(-1), 42 m_prevX(0),
48 m_rx(0), 43 m_requestToken(-1)
49 m_rw(0), 44 {
50 m_userRegion(false), 45 m_cache.data = 0;
51 m_region(0)
52 {
53 // Ensure header size is a multiple of the size of our data (for
54 // alignment purposes)
55 size_t hs = ((m_headerSize / sizeof(float)) * sizeof(float));
56 if (hs != m_headerSize) m_headerSize = hs + sizeof(float);
57 46
58 QDir tempDir(TempDirectory::instance()->getPath()); 47 QDir tempDir(TempDirectory::instance()->getPath());
59 QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase))); 48 QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
60 bool newFile = !QFileInfo(fileName).exists(); 49 bool newFile = !QFileInfo(fileName).exists();
61 50
99 m_height = header[1]; 88 m_height = header[1];
100 seekTo(0, 0); 89 seekTo(0, 0);
101 } 90 }
102 91
103 m_fileName = fileName; 92 m_fileName = fileName;
93
94 //!!! why isn't this signal being delivered?
95 connect(&m_readThread, SIGNAL(cancelled(int)),
96 this, SLOT(requestCancelled(int)));
97
98 m_readThread.start();
99
104 QMutexLocker locker(&m_refcountMutex); 100 QMutexLocker locker(&m_refcountMutex);
105 ++m_refcount[fileName]; 101 ++m_refcount[fileName];
106 102
107 std::cerr << "MatrixFileCache::MatrixFileCache: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl; 103 std::cerr << "MatrixFileCache::MatrixFileCache: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl;
108 104
109 } 105 }
110 106
111 MatrixFileCache::~MatrixFileCache() 107 MatrixFileCache::~MatrixFileCache()
112 { 108 {
113 if (m_rw > 0) { 109 float *requestData = 0;
114 delete[] m_region; 110
115 } 111 if (m_requestToken >= 0) {
112 FileReadThread::Request request;
113 if (m_readThread.getRequest(m_requestToken, request)) {
114 requestData = (float *)request.data;
115 }
116 }
117
118 m_readThread.finish();
119 m_readThread.wait();
120
121 if (requestData) delete[] requestData;
122 if (m_cache.data) delete[] m_cache.data;
116 123
117 if (m_fd >= 0) { 124 if (m_fd >= 0) {
118 if (::close(m_fd) < 0) { 125 if (::close(m_fd) < 0) {
119 ::perror("MatrixFileCache::~MatrixFileCache: close failed"); 126 ::perror("MatrixFileCache::~MatrixFileCache: close failed");
120 } 127 }
121 } 128 }
122 129
123 if (m_fileName != "") { 130 if (m_fileName != "") {
124 QMutexLocker locker(&m_refcountMutex); 131 QMutexLocker locker(&m_refcountMutex);
125 if (--m_refcount[m_fileName] == 0) { 132 if (--m_refcount[m_fileName] == 0) {
126 if (!QFile(m_fileName).remove()) { 133 if (::unlink(m_fileName.toLocal8Bit())) {
134 ::perror("Unlink failed");
127 std::cerr << "WARNING: MatrixFileCache::~MatrixFileCache: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl; 135 std::cerr << "WARNING: MatrixFileCache::~MatrixFileCache: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl;
128 } else { 136 } else {
129 std::cerr << "deleted " << m_fileName.toStdString() << std::endl; 137 std::cerr << "deleted " << m_fileName.toStdString() << std::endl;
130 } 138 }
131 } 139 }
151 std::cerr << "ERROR: MatrixFileCache::resize called on read-only cache" 159 std::cerr << "ERROR: MatrixFileCache::resize called on read-only cache"
152 << std::endl; 160 << std::endl;
153 return; 161 return;
154 } 162 }
155 163
164 QMutexLocker locker(&m_fdMutex);
165
156 off_t off = m_headerSize + (w * h * sizeof(float)); 166 off_t off = m_headerSize + (w * h * sizeof(float));
157 167
158 if (w * h > m_width * m_height) { 168 if (w * h > m_width * m_height) {
159 169
160 /*!!! 170 if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) {
161 // If we're going to mmap the file, we need to ensure it's long 171 ::perror("Seek failed");
162 // enough beforehand 172 std::cerr << "ERROR: MatrixFileCache::resize(" << w << ", "
173 << h << "): seek failed, cannot resize" << std::endl;
174 return;
175 }
176
177 // guess this requires efficient support for sparse files
163 178
164 if (m_preferMmap) { 179 float f(0);
165 180 if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) {
166 if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) { 181 ::perror("WARNING: MatrixFileCache::resize: write failed");
167 ::perror("Seek failed"); 182 }
168 std::cerr << "ERROR: MatrixFileCache::resize(" << w << ", " 183
169 << h << "): seek failed, cannot resize" << std::endl;
170 return;
171 }
172
173 // guess this requires efficient support for sparse files
174
175 float f(0);
176 if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) {
177 ::perror("WARNING: MatrixFileCache::resize: write failed");
178 }
179 }
180 */
181 } else { 184 } else {
182 185
183 if (::ftruncate(m_fd, off) < 0) { 186 if (::ftruncate(m_fd, off) < 0) {
184 ::perror("WARNING: MatrixFileCache::resize: ftruncate failed"); 187 ::perror("WARNING: MatrixFileCache::resize: ftruncate failed");
185 } 188 }
186 } 189 }
187 190
188 m_width = 0; 191 m_width = 0;
189 m_height = 0; 192 m_height = 0;
190 m_off = 0;
191 193
192 if (::lseek(m_fd, 0, SEEK_SET) == (off_t)-1) { 194 if (::lseek(m_fd, 0, SEEK_SET) == (off_t)-1) {
193 ::perror("ERROR: MatrixFileCache::resize: Seek to write header failed"); 195 ::perror("ERROR: MatrixFileCache::resize: Seek to write header failed");
194 return; 196 return;
195 } 197 }
215 std::cerr << "ERROR: MatrixFileCache::reset called on read-only cache" 217 std::cerr << "ERROR: MatrixFileCache::reset called on read-only cache"
216 << std::endl; 218 << std::endl;
217 return; 219 return;
218 } 220 }
219 221
222 QMutexLocker locker(&m_fdMutex);
223
220 float *emptyCol = new float[m_height]; 224 float *emptyCol = new float[m_height];
221 for (size_t y = 0; y < m_height; ++y) emptyCol[y] = 0.f; 225 for (size_t y = 0; y < m_height; ++y) emptyCol[y] = 0.f;
222 226
223 seekTo(0, 0); 227 seekTo(0, 0);
224 for (size_t x = 0; x < m_width; ++x) setColumnAt(x, emptyCol); 228 for (size_t x = 0; x < m_width; ++x) setColumnAt(x, emptyCol);
225 229
226 delete[] emptyCol; 230 delete[] emptyCol;
227 } 231 }
228 232
229 void
230 MatrixFileCache::setRegionOfInterest(size_t x, size_t width)
231 {
232 setRegion(x, width, true);
233 }
234
235 void
236 MatrixFileCache::clearRegionOfInterest()
237 {
238 m_userRegion = false;
239 }
240
241 float 233 float
242 MatrixFileCache::getValueAt(size_t x, size_t y) const 234 MatrixFileCache::getValueAt(size_t x, size_t y)
243 { 235 {
244 if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) { 236 float value = 0.f;
245 float *rp = getRegionPtr(x, y); 237 if (getValuesFromCache(x, y, 1, &value)) return value;
246 if (rp) return *rp; 238
247 } else if (!m_userRegion) { 239 ssize_t r = 0;
248 if (autoSetRegion(x)) { 240
249 float *rp = getRegionPtr(x, y); 241 // std::cout << "MatrixFileCache::getValueAt(" << x << ", " << y << ")"
250 if (rp) return *rp; 242 // << ": reading the slow way" << std::endl;
251 else return 0.f; 243
252 } 244 m_fdMutex.lock();
253 } 245
254 246 if (seekTo(x, y)) {
255 if (!seekTo(x, y)) return 0.f; 247 r = ::read(m_fd, &value, sizeof(float));
256 float value; 248 }
257 ssize_t r = ::read(m_fd, &value, sizeof(float)); 249
250 m_fdMutex.unlock();
251
258 if (r < 0) { 252 if (r < 0) {
259 ::perror("MatrixFileCache::getValueAt: Read failed"); 253 ::perror("MatrixFileCache::getValueAt: Read failed");
260 } 254 }
261 if (r != sizeof(float)) { 255 if (r != sizeof(float)) {
262 value = 0.f; 256 value = 0.f;
263 } 257 }
264 if (r > 0) m_off += r; 258
265 return value; 259 return value;
266 } 260 }
267 261
268 void 262 void
269 MatrixFileCache::getColumnAt(size_t x, float *values) const 263 MatrixFileCache::getColumnAt(size_t x, float *values)
270 { 264 {
271 if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) { 265 if (getValuesFromCache(x, 0, m_height, values)) return;
272 float *rp = getRegionPtr(x, 0); 266
273 if (rp) { 267 ssize_t r = 0;
274 for (size_t y = 0; y < m_height; ++y) { 268
275 values[y] = rp[y]; 269 std::cout << "MatrixFileCache::getColumnAt(" << x << ")"
276 } 270 << ": reading the slow way" << std::endl;
277 } 271
278 return; 272 m_fdMutex.lock();
279 } else if (!m_userRegion) { 273
280 if (autoSetRegion(x)) { 274 if (seekTo(x, 0)) {
281 float *rp = getRegionPtr(x, 0); 275 r = ::read(m_fd, values, m_height * sizeof(float));
282 if (rp) { 276 }
283 for (size_t y = 0; y < m_height; ++y) { 277
284 values[y] = rp[y]; 278 m_fdMutex.unlock();
285 } 279
286 return;
287 }
288 }
289 }
290
291 if (!seekTo(x, 0)) return;
292 ssize_t r = ::read(m_fd, values, m_height * sizeof(float));
293 if (r < 0) { 280 if (r < 0) {
294 ::perror("MatrixFileCache::getColumnAt: read failed"); 281 ::perror("MatrixFileCache::getColumnAt: read failed");
295 } 282 }
296 if (r > 0) m_off += r; 283 }
284
285 bool
286 MatrixFileCache::getValuesFromCache(size_t x, size_t ystart, size_t ycount,
287 float *values)
288 {
289 m_cacheMutex.lock();
290
291 if (!m_cache.data || x < m_cache.x || x >= m_cache.x + m_cache.width) {
292 bool left = (m_cache.data && x < m_cache.x);
293 m_cacheMutex.unlock();
294 primeCache(x, left); // this doesn't take effect until a later callback
295 m_prevX = x;
296 return false;
297 }
298
299 for (size_t y = ystart; y < ystart + ycount; ++y) {
300 values[y - ystart] = m_cache.data[(x - m_cache.x) * m_height + y];
301 }
302 m_cacheMutex.unlock();
303
304 if (m_cache.x > 0 && x < m_prevX && x < m_cache.x + m_cache.width/4) {
305 primeCache(x, true);
306 }
307
308 if (m_cache.x + m_cache.width < m_width &&
309 x > m_prevX &&
310 x > m_cache.x + (m_cache.width * 3) / 4) {
311 primeCache(x, false);
312 }
313
314 m_prevX = x;
315 return true;
297 } 316 }
298 317
299 void 318 void
300 MatrixFileCache::setValueAt(size_t x, size_t y, float value) 319 MatrixFileCache::setValueAt(size_t x, size_t y, float value)
301 { 320 {
303 std::cerr << "ERROR: MatrixFileCache::setValueAt called on read-only cache" 322 std::cerr << "ERROR: MatrixFileCache::setValueAt called on read-only cache"
304 << std::endl; 323 << std::endl;
305 return; 324 return;
306 } 325 }
307 326
308 if (!seekTo(x, y)) return; 327 ssize_t w = 0;
309 ssize_t w = ::write(m_fd, &value, sizeof(float)); 328 bool seekFailed = false;
310 if (w != sizeof(float)) { 329
330 m_fdMutex.lock();
331
332 if (seekTo(x, y)) {
333 w = ::write(m_fd, &value, sizeof(float));
334 } else {
335 seekFailed = true;
336 }
337
338 m_fdMutex.unlock();
339
340 if (!seekFailed && w != sizeof(float)) {
311 ::perror("WARNING: MatrixFileCache::setValueAt: write failed"); 341 ::perror("WARNING: MatrixFileCache::setValueAt: write failed");
312 } 342 }
313 if (w > 0) m_off += w; 343
314 344 //... update cache as appropriate
315 //... update region as appropriate
316 } 345 }
317 346
318 void 347 void
319 MatrixFileCache::setColumnAt(size_t x, float *values) 348 MatrixFileCache::setColumnAt(size_t x, float *values)
320 { 349 {
322 std::cerr << "ERROR: MatrixFileCache::setColumnAt called on read-only cache" 351 std::cerr << "ERROR: MatrixFileCache::setColumnAt called on read-only cache"
323 << std::endl; 352 << std::endl;
324 return; 353 return;
325 } 354 }
326 355
327 if (!seekTo(x, 0)) return; 356 ssize_t w = 0;
328 ssize_t w = ::write(m_fd, values, m_height * sizeof(float)); 357 bool seekFailed = false;
329 if (w != ssize_t(m_height * sizeof(float))) { 358
359 m_fdMutex.lock();
360
361 if (seekTo(x, 0)) {
362 w = ::write(m_fd, values, m_height * sizeof(float));
363 } else {
364 seekFailed = true;
365 }
366
367 m_fdMutex.unlock();
368
369 if (!seekFailed && w != ssize_t(m_height * sizeof(float))) {
330 ::perror("WARNING: MatrixFileCache::setColumnAt: write failed"); 370 ::perror("WARNING: MatrixFileCache::setColumnAt: write failed");
331 } 371 }
332 if (w > 0) m_off += w; 372
333 373 //... update cache as appropriate
334 //... update region as appropriate 374 }
335 } 375
336 376 void
337 float * 377 MatrixFileCache::primeCache(size_t x, bool goingLeft)
338 MatrixFileCache::getRegionPtr(size_t x, size_t y) const 378 {
339 { 379 // std::cerr << "MatrixFileCache::primeCache(" << x << ", " << goingLeft << ")" << std::endl;
340 if (m_rw == 0) return 0; 380
341
342 float *region = m_region;
343
344 float *ptr = &(region[(x - m_rx) * m_height + y]);
345
346 // std::cerr << "getRegionPtr(" << x << "," << y << "): region is " << m_region << ", returning " << ptr << std::endl;
347 return ptr;
348 }
349
350 bool
351 MatrixFileCache::autoSetRegion(size_t x) const
352 {
353 size_t rx = x; 381 size_t rx = x;
354 size_t rw = m_autoRegionWidth; 382 size_t rw = m_defaultCacheWidth;
355 size_t left = rw / 4; 383
356 if (x < m_rx) left = (rw * 3) / 4; 384 size_t left = rw / 3;
385 if (goingLeft) left = (rw * 2) / 3;
386
357 if (rx > left) rx -= left; 387 if (rx > left) rx -= left;
358 else rx = 0; 388 else rx = 0;
389
359 if (rx + rw > m_width) rw = m_width - rx; 390 if (rx + rw > m_width) rw = m_width - rx;
360 return setRegion(rx, rw, false); 391
392 QMutexLocker locker(&m_cacheMutex);
393
394 if (m_requestToken >= 0) {
395
396 if (x >= m_requestingX &&
397 x < m_requestingX + m_requestingWidth) {
398
399 if (m_readThread.isReady(m_requestToken)) {
400
401 std::cerr << "last request is ready! (" << m_requestingX << ", "<< m_requestingWidth << ")" << std::endl;
402
403 FileReadThread::Request request;
404 if (m_readThread.getRequest(m_requestToken, request)) {
405
406 m_cache.x = (request.start - m_headerSize) / (m_height * sizeof(float));
407 m_cache.width = request.size / (m_height * sizeof(float));
408
409 std::cerr << "actual: " << m_cache.x << ", " << m_cache.width << std::endl;
410
411 if (m_cache.data) delete[] m_cache.data;
412 m_cache.data = (float *)request.data;
413 }
414
415 m_readThread.done(m_requestToken);
416 m_requestToken = -1;
417 }
418
419 return;
420 }
421
422 std::cerr << "cancelling last request" << std::endl;
423 m_readThread.cancel(m_requestToken);
424 //!!!
425 m_requestToken = -1;
426 }
427
428 FileReadThread::Request request;
429 request.fd = m_fd;
430 request.mutex = &m_fdMutex;
431 request.start = m_headerSize + rx * m_height * sizeof(float);
432 request.size = rw * m_height * sizeof(float);
433 request.data = (char *)(new float[rw * m_height]);
434
435 m_requestingX = rx;
436 m_requestingWidth = rw;
437
438 int token = m_readThread.request(request);
439 std::cerr << "MatrixFileCache::primeCache: request token is "
440 << token << std::endl;
441
442 m_requestToken = token;
443 }
444
445 void
446 MatrixFileCache::requestCancelled(int token)
447 {
448 std::cerr << "MatrixFileCache::requestCancelled(" << token << ")" << std::endl;
449
450 FileReadThread::Request request;
451 if (m_readThread.getRequest(token, request)) {
452 delete[] ((float *)request.data);
453 m_readThread.done(token);
454 }
361 } 455 }
362 456
363 bool 457 bool
364 MatrixFileCache::setRegion(size_t x, size_t width, bool user) const 458 MatrixFileCache::seekTo(size_t x, size_t y)
365 {
366 if (!user && m_userRegion) return false;
367 if (m_rw > 0 && x >= m_rx && x + width <= m_rx + m_rw) return true;
368
369 if (m_rw > 0) {
370 delete[] m_region;
371 m_region = 0;
372 m_rw = 0;
373 }
374
375 if (width == 0) {
376 return true;
377 }
378
379 if (!seekTo(x, 0)) return false;
380
381 m_region = new float[width * m_height];
382 MUNLOCK(m_region, width * m_height * sizeof(float));
383
384 ssize_t r = ::read(m_fd, m_region, width * m_height * sizeof(float));
385 if (r < 0) {
386 ::perror("Read failed");
387 std::cerr << "ERROR: MatrixFileCache::setRegion(" << x << ", " << width
388 << ") failed" << std::endl;
389 delete[] m_region;
390 m_region = 0;
391 return false;
392 }
393
394 m_off += r;
395
396 if (r < ssize_t(width * m_height * sizeof(float))) {
397 // didn't manage to read the whole thing, but did get something
398 std::cerr << "WARNING: MatrixFileCache::setRegion(" << x << ", " << width
399 << "): ";
400 width = r / (m_height * sizeof(float));
401 std::cerr << "Only got " << width << " columns" << std::endl;
402 }
403
404 m_rx = x;
405 m_rw = width;
406 if (m_rw == 0) {
407 delete[] m_region;
408 m_region = 0;
409 }
410
411 std::cerr << "MatrixFileCache::setRegion: set region to " << x << ", " << width << std::endl;
412
413 if (user) m_userRegion = true;
414 return true;
415 }
416
417 bool
418 MatrixFileCache::seekTo(size_t x, size_t y) const
419 { 459 {
420 off_t off = m_headerSize + (x * m_height + y) * sizeof(float); 460 off_t off = m_headerSize + (x * m_height + y) * sizeof(float);
421 if (off == m_off) return true;
422
423 if (m_mode == ReadWrite) {
424 std::cerr << "writer: ";
425 std::cerr << "seek required (from " << m_off << " to " << off << ")" << std::endl;
426 }
427 461
428 if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) { 462 if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
429 ::perror("Seek failed"); 463 ::perror("Seek failed");
430 std::cerr << "ERROR: MatrixFileCache::seekTo(" << x << ", " << y 464 std::cerr << "ERROR: MatrixFileCache::seekTo(" << x << ", " << y
431 << ") failed" << std::endl; 465 << ") failed" << std::endl;
432 return false; 466 return false;
433 } 467 }
434 468
435 m_off = off;
436 return true; 469 return true;
437 } 470 }
438 471