annotate 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
rev   line source
Chris@87 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@87 2
Chris@87 3 /*
Chris@87 4 Sonic Visualiser
Chris@87 5 An audio file viewer and annotation editor.
Chris@87 6 Centre for Digital Music, Queen Mary, University of London.
Chris@87 7 This file copyright 2006 Chris Cannam.
Chris@87 8
Chris@87 9 This program is free software; you can redistribute it and/or
Chris@87 10 modify it under the terms of the GNU General Public License as
Chris@87 11 published by the Free Software Foundation; either version 2 of the
Chris@87 12 License, or (at your option) any later version. See the file
Chris@87 13 COPYING included with this distribution for more information.
Chris@87 14 */
Chris@87 15
Chris@87 16 #include "MatrixFileCache.h"
Chris@87 17 #include "base/TempDirectory.h"
Chris@90 18 #include "base/System.h"
Chris@87 19
Chris@87 20 #include <sys/types.h>
Chris@87 21 #include <sys/stat.h>
Chris@87 22 #include <fcntl.h>
Chris@87 23 #include <unistd.h>
Chris@87 24
Chris@87 25 #include <iostream>
Chris@87 26
Chris@87 27 #include <cstdio>
Chris@87 28
Chris@87 29 #include <QFileInfo>
Chris@87 30 #include <QDir>
Chris@87 31
Chris@91 32 std::map<QString, int> MatrixFileCache::m_refcount;
Chris@91 33 QMutex MatrixFileCache::m_refcountMutex;
Chris@91 34
Chris@87 35 MatrixFileCache::MatrixFileCache(QString fileBase, Mode mode) :
Chris@87 36 m_fd(-1),
Chris@87 37 m_mode(mode),
Chris@87 38 m_width(0),
Chris@87 39 m_height(0),
Chris@90 40 m_headerSize(2 * sizeof(size_t)),
Chris@95 41 m_defaultCacheWidth(2048),
Chris@95 42 m_prevX(0),
Chris@95 43 m_requestToken(-1)
Chris@87 44 {
Chris@95 45 m_cache.data = 0;
Chris@90 46
Chris@87 47 QDir tempDir(TempDirectory::instance()->getPath());
Chris@87 48 QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
Chris@87 49 bool newFile = !QFileInfo(fileName).exists();
Chris@87 50
Chris@87 51 if (newFile && mode == ReadOnly) {
Chris@87 52 std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: Read-only mode "
Chris@87 53 << "specified, but cache file does not exist" << std::endl;
Chris@87 54 return;
Chris@87 55 }
Chris@87 56
Chris@87 57 int flags = 0;
Chris@87 58 mode_t fmode = S_IRUSR | S_IWUSR;
Chris@87 59
Chris@87 60 if (mode == ReadWrite) {
Chris@87 61 flags = O_RDWR | O_CREAT;
Chris@87 62 } else {
Chris@87 63 flags = O_RDONLY;
Chris@87 64 }
Chris@87 65
Chris@90 66 if ((m_fd = ::open(fileName.toLocal8Bit(), flags, fmode)) < 0) {
Chris@87 67 ::perror("Open failed");
Chris@87 68 std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: "
Chris@87 69 << "Failed to open cache file \""
Chris@87 70 << fileName.toStdString() << "\"";
Chris@87 71 if (mode == ReadWrite) std::cerr << " for writing";
Chris@87 72 std::cerr << std::endl;
Chris@91 73 return;
Chris@87 74 }
Chris@87 75
Chris@87 76 if (newFile) {
Chris@87 77 resize(0, 0); // write header
Chris@87 78 } else {
Chris@87 79 size_t header[2];
Chris@90 80 if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) {
Chris@87 81 perror("Read failed");
Chris@87 82 std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: "
Chris@90 83 << "Failed to read header (fd " << m_fd << ", file \""
Chris@90 84 << fileName.toStdString() << "\")" << std::endl;
Chris@87 85 return;
Chris@87 86 }
Chris@87 87 m_width = header[0];
Chris@87 88 m_height = header[1];
Chris@87 89 seekTo(0, 0);
Chris@87 90 }
Chris@87 91
Chris@91 92 m_fileName = fileName;
Chris@95 93
Chris@95 94 //!!! why isn't this signal being delivered?
Chris@95 95 connect(&m_readThread, SIGNAL(cancelled(int)),
Chris@95 96 this, SLOT(requestCancelled(int)));
Chris@95 97
Chris@95 98 m_readThread.start();
Chris@95 99
Chris@91 100 QMutexLocker locker(&m_refcountMutex);
Chris@91 101 ++m_refcount[fileName];
Chris@91 102
Chris@90 103 std::cerr << "MatrixFileCache::MatrixFileCache: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl;
Chris@87 104
Chris@87 105 }
Chris@87 106
Chris@87 107 MatrixFileCache::~MatrixFileCache()
Chris@87 108 {
Chris@95 109 float *requestData = 0;
Chris@95 110
Chris@95 111 if (m_requestToken >= 0) {
Chris@95 112 FileReadThread::Request request;
Chris@95 113 if (m_readThread.getRequest(m_requestToken, request)) {
Chris@95 114 requestData = (float *)request.data;
Chris@95 115 }
Chris@90 116 }
Chris@90 117
Chris@95 118 m_readThread.finish();
Chris@95 119 m_readThread.wait();
Chris@95 120
Chris@95 121 if (requestData) delete[] requestData;
Chris@95 122 if (m_cache.data) delete[] m_cache.data;
Chris@95 123
Chris@87 124 if (m_fd >= 0) {
Chris@87 125 if (::close(m_fd) < 0) {
Chris@87 126 ::perror("MatrixFileCache::~MatrixFileCache: close failed");
Chris@87 127 }
Chris@87 128 }
Chris@90 129
Chris@91 130 if (m_fileName != "") {
Chris@91 131 QMutexLocker locker(&m_refcountMutex);
Chris@91 132 if (--m_refcount[m_fileName] == 0) {
Chris@95 133 if (::unlink(m_fileName.toLocal8Bit())) {
Chris@95 134 ::perror("Unlink failed");
Chris@91 135 std::cerr << "WARNING: MatrixFileCache::~MatrixFileCache: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl;
Chris@91 136 } else {
Chris@91 137 std::cerr << "deleted " << m_fileName.toStdString() << std::endl;
Chris@91 138 }
Chris@91 139 }
Chris@91 140 }
Chris@87 141 }
Chris@87 142
Chris@87 143 size_t
Chris@87 144 MatrixFileCache::getWidth() const
Chris@87 145 {
Chris@87 146 return m_width;
Chris@87 147 }
Chris@87 148
Chris@87 149 size_t
Chris@87 150 MatrixFileCache::getHeight() const
Chris@87 151 {
Chris@87 152 return m_height;
Chris@87 153 }
Chris@87 154
Chris@87 155 void
Chris@87 156 MatrixFileCache::resize(size_t w, size_t h)
Chris@87 157 {
Chris@87 158 if (m_mode != ReadWrite) {
Chris@87 159 std::cerr << "ERROR: MatrixFileCache::resize called on read-only cache"
Chris@87 160 << std::endl;
Chris@87 161 return;
Chris@87 162 }
Chris@87 163
Chris@95 164 QMutexLocker locker(&m_fdMutex);
Chris@95 165
Chris@87 166 off_t off = m_headerSize + (w * h * sizeof(float));
Chris@87 167
Chris@87 168 if (w * h > m_width * m_height) {
Chris@87 169
Chris@95 170 if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) {
Chris@95 171 ::perror("Seek failed");
Chris@95 172 std::cerr << "ERROR: MatrixFileCache::resize(" << w << ", "
Chris@95 173 << h << "): seek failed, cannot resize" << std::endl;
Chris@95 174 return;
Chris@95 175 }
Chris@95 176
Chris@95 177 // guess this requires efficient support for sparse files
Chris@90 178
Chris@95 179 float f(0);
Chris@95 180 if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) {
Chris@95 181 ::perror("WARNING: MatrixFileCache::resize: write failed");
Chris@87 182 }
Chris@95 183
Chris@87 184 } else {
Chris@87 185
Chris@87 186 if (::ftruncate(m_fd, off) < 0) {
Chris@90 187 ::perror("WARNING: MatrixFileCache::resize: ftruncate failed");
Chris@87 188 }
Chris@87 189 }
Chris@87 190
Chris@87 191 m_width = 0;
Chris@87 192 m_height = 0;
Chris@87 193
Chris@87 194 if (::lseek(m_fd, 0, SEEK_SET) == (off_t)-1) {
Chris@87 195 ::perror("ERROR: MatrixFileCache::resize: Seek to write header failed");
Chris@87 196 return;
Chris@87 197 }
Chris@87 198
Chris@87 199 size_t header[2];
Chris@87 200 header[0] = w;
Chris@87 201 header[1] = h;
Chris@87 202 if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) {
Chris@87 203 ::perror("ERROR: MatrixFileCache::resize: Failed to write header");
Chris@87 204 return;
Chris@87 205 }
Chris@87 206
Chris@87 207 m_width = w;
Chris@87 208 m_height = h;
Chris@87 209
Chris@87 210 seekTo(0, 0);
Chris@87 211 }
Chris@87 212
Chris@87 213 void
Chris@87 214 MatrixFileCache::reset()
Chris@87 215 {
Chris@87 216 if (m_mode != ReadWrite) {
Chris@87 217 std::cerr << "ERROR: MatrixFileCache::reset called on read-only cache"
Chris@87 218 << std::endl;
Chris@87 219 return;
Chris@87 220 }
Chris@87 221
Chris@95 222 QMutexLocker locker(&m_fdMutex);
Chris@95 223
Chris@90 224 float *emptyCol = new float[m_height];
Chris@90 225 for (size_t y = 0; y < m_height; ++y) emptyCol[y] = 0.f;
Chris@90 226
Chris@90 227 seekTo(0, 0);
Chris@90 228 for (size_t x = 0; x < m_width; ++x) setColumnAt(x, emptyCol);
Chris@90 229
Chris@90 230 delete[] emptyCol;
Chris@87 231 }
Chris@87 232
Chris@95 233 float
Chris@95 234 MatrixFileCache::getValueAt(size_t x, size_t y)
Chris@87 235 {
Chris@95 236 float value = 0.f;
Chris@95 237 if (getValuesFromCache(x, y, 1, &value)) return value;
Chris@90 238
Chris@95 239 ssize_t r = 0;
Chris@87 240
Chris@95 241 // std::cout << "MatrixFileCache::getValueAt(" << x << ", " << y << ")"
Chris@95 242 // << ": reading the slow way" << std::endl;
Chris@95 243
Chris@95 244 m_fdMutex.lock();
Chris@95 245
Chris@95 246 if (seekTo(x, y)) {
Chris@95 247 r = ::read(m_fd, &value, sizeof(float));
Chris@87 248 }
Chris@87 249
Chris@95 250 m_fdMutex.unlock();
Chris@95 251
Chris@91 252 if (r < 0) {
Chris@91 253 ::perror("MatrixFileCache::getValueAt: Read failed");
Chris@91 254 }
Chris@90 255 if (r != sizeof(float)) {
Chris@90 256 value = 0.f;
Chris@87 257 }
Chris@95 258
Chris@87 259 return value;
Chris@87 260 }
Chris@87 261
Chris@87 262 void
Chris@95 263 MatrixFileCache::getColumnAt(size_t x, float *values)
Chris@87 264 {
Chris@95 265 if (getValuesFromCache(x, 0, m_height, values)) return;
Chris@95 266
Chris@95 267 ssize_t r = 0;
Chris@95 268
Chris@95 269 std::cout << "MatrixFileCache::getColumnAt(" << x << ")"
Chris@95 270 << ": reading the slow way" << std::endl;
Chris@95 271
Chris@95 272 m_fdMutex.lock();
Chris@95 273
Chris@95 274 if (seekTo(x, 0)) {
Chris@95 275 r = ::read(m_fd, values, m_height * sizeof(float));
Chris@87 276 }
Chris@87 277
Chris@95 278 m_fdMutex.unlock();
Chris@95 279
Chris@91 280 if (r < 0) {
Chris@87 281 ::perror("MatrixFileCache::getColumnAt: read failed");
Chris@87 282 }
Chris@95 283 }
Chris@95 284
Chris@95 285 bool
Chris@95 286 MatrixFileCache::getValuesFromCache(size_t x, size_t ystart, size_t ycount,
Chris@95 287 float *values)
Chris@95 288 {
Chris@95 289 m_cacheMutex.lock();
Chris@95 290
Chris@95 291 if (!m_cache.data || x < m_cache.x || x >= m_cache.x + m_cache.width) {
Chris@95 292 bool left = (m_cache.data && x < m_cache.x);
Chris@95 293 m_cacheMutex.unlock();
Chris@95 294 primeCache(x, left); // this doesn't take effect until a later callback
Chris@95 295 m_prevX = x;
Chris@95 296 return false;
Chris@95 297 }
Chris@95 298
Chris@95 299 for (size_t y = ystart; y < ystart + ycount; ++y) {
Chris@95 300 values[y - ystart] = m_cache.data[(x - m_cache.x) * m_height + y];
Chris@95 301 }
Chris@95 302 m_cacheMutex.unlock();
Chris@95 303
Chris@95 304 if (m_cache.x > 0 && x < m_prevX && x < m_cache.x + m_cache.width/4) {
Chris@95 305 primeCache(x, true);
Chris@95 306 }
Chris@95 307
Chris@95 308 if (m_cache.x + m_cache.width < m_width &&
Chris@95 309 x > m_prevX &&
Chris@95 310 x > m_cache.x + (m_cache.width * 3) / 4) {
Chris@95 311 primeCache(x, false);
Chris@95 312 }
Chris@95 313
Chris@95 314 m_prevX = x;
Chris@95 315 return true;
Chris@87 316 }
Chris@87 317
Chris@87 318 void
Chris@87 319 MatrixFileCache::setValueAt(size_t x, size_t y, float value)
Chris@87 320 {
Chris@87 321 if (m_mode != ReadWrite) {
Chris@87 322 std::cerr << "ERROR: MatrixFileCache::setValueAt called on read-only cache"
Chris@87 323 << std::endl;
Chris@87 324 return;
Chris@87 325 }
Chris@87 326
Chris@95 327 ssize_t w = 0;
Chris@95 328 bool seekFailed = false;
Chris@95 329
Chris@95 330 m_fdMutex.lock();
Chris@95 331
Chris@95 332 if (seekTo(x, y)) {
Chris@95 333 w = ::write(m_fd, &value, sizeof(float));
Chris@95 334 } else {
Chris@95 335 seekFailed = true;
Chris@95 336 }
Chris@95 337
Chris@95 338 m_fdMutex.unlock();
Chris@95 339
Chris@95 340 if (!seekFailed && w != sizeof(float)) {
Chris@87 341 ::perror("WARNING: MatrixFileCache::setValueAt: write failed");
Chris@87 342 }
Chris@87 343
Chris@95 344 //... update cache as appropriate
Chris@87 345 }
Chris@87 346
Chris@87 347 void
Chris@87 348 MatrixFileCache::setColumnAt(size_t x, float *values)
Chris@87 349 {
Chris@87 350 if (m_mode != ReadWrite) {
Chris@87 351 std::cerr << "ERROR: MatrixFileCache::setColumnAt called on read-only cache"
Chris@87 352 << std::endl;
Chris@87 353 return;
Chris@87 354 }
Chris@87 355
Chris@95 356 ssize_t w = 0;
Chris@95 357 bool seekFailed = false;
Chris@95 358
Chris@95 359 m_fdMutex.lock();
Chris@95 360
Chris@95 361 if (seekTo(x, 0)) {
Chris@95 362 w = ::write(m_fd, values, m_height * sizeof(float));
Chris@95 363 } else {
Chris@95 364 seekFailed = true;
Chris@95 365 }
Chris@95 366
Chris@95 367 m_fdMutex.unlock();
Chris@95 368
Chris@95 369 if (!seekFailed && w != ssize_t(m_height * sizeof(float))) {
Chris@87 370 ::perror("WARNING: MatrixFileCache::setColumnAt: write failed");
Chris@87 371 }
Chris@87 372
Chris@95 373 //... update cache as appropriate
Chris@90 374 }
Chris@90 375
Chris@95 376 void
Chris@95 377 MatrixFileCache::primeCache(size_t x, bool goingLeft)
Chris@90 378 {
Chris@95 379 // std::cerr << "MatrixFileCache::primeCache(" << x << ", " << goingLeft << ")" << std::endl;
Chris@90 380
Chris@95 381 size_t rx = x;
Chris@95 382 size_t rw = m_defaultCacheWidth;
Chris@90 383
Chris@95 384 size_t left = rw / 3;
Chris@95 385 if (goingLeft) left = (rw * 2) / 3;
Chris@95 386
Chris@95 387 if (rx > left) rx -= left;
Chris@95 388 else rx = 0;
Chris@95 389
Chris@95 390 if (rx + rw > m_width) rw = m_width - rx;
Chris@95 391
Chris@95 392 QMutexLocker locker(&m_cacheMutex);
Chris@95 393
Chris@95 394 if (m_requestToken >= 0) {
Chris@95 395
Chris@95 396 if (x >= m_requestingX &&
Chris@95 397 x < m_requestingX + m_requestingWidth) {
Chris@95 398
Chris@95 399 if (m_readThread.isReady(m_requestToken)) {
Chris@95 400
Chris@95 401 std::cerr << "last request is ready! (" << m_requestingX << ", "<< m_requestingWidth << ")" << std::endl;
Chris@95 402
Chris@95 403 FileReadThread::Request request;
Chris@95 404 if (m_readThread.getRequest(m_requestToken, request)) {
Chris@95 405
Chris@95 406 m_cache.x = (request.start - m_headerSize) / (m_height * sizeof(float));
Chris@95 407 m_cache.width = request.size / (m_height * sizeof(float));
Chris@95 408
Chris@95 409 std::cerr << "actual: " << m_cache.x << ", " << m_cache.width << std::endl;
Chris@95 410
Chris@95 411 if (m_cache.data) delete[] m_cache.data;
Chris@95 412 m_cache.data = (float *)request.data;
Chris@95 413 }
Chris@95 414
Chris@95 415 m_readThread.done(m_requestToken);
Chris@95 416 m_requestToken = -1;
Chris@95 417 }
Chris@95 418
Chris@95 419 return;
Chris@95 420 }
Chris@95 421
Chris@95 422 std::cerr << "cancelling last request" << std::endl;
Chris@95 423 m_readThread.cancel(m_requestToken);
Chris@95 424 //!!!
Chris@95 425 m_requestToken = -1;
Chris@95 426 }
Chris@95 427
Chris@95 428 FileReadThread::Request request;
Chris@95 429 request.fd = m_fd;
Chris@95 430 request.mutex = &m_fdMutex;
Chris@95 431 request.start = m_headerSize + rx * m_height * sizeof(float);
Chris@95 432 request.size = rw * m_height * sizeof(float);
Chris@95 433 request.data = (char *)(new float[rw * m_height]);
Chris@95 434
Chris@95 435 m_requestingX = rx;
Chris@95 436 m_requestingWidth = rw;
Chris@95 437
Chris@95 438 int token = m_readThread.request(request);
Chris@95 439 std::cerr << "MatrixFileCache::primeCache: request token is "
Chris@95 440 << token << std::endl;
Chris@95 441
Chris@95 442 m_requestToken = token;
Chris@95 443 }
Chris@95 444
Chris@95 445 void
Chris@95 446 MatrixFileCache::requestCancelled(int token)
Chris@95 447 {
Chris@95 448 std::cerr << "MatrixFileCache::requestCancelled(" << token << ")" << std::endl;
Chris@95 449
Chris@95 450 FileReadThread::Request request;
Chris@95 451 if (m_readThread.getRequest(token, request)) {
Chris@95 452 delete[] ((float *)request.data);
Chris@95 453 m_readThread.done(token);
Chris@95 454 }
Chris@90 455 }
Chris@90 456
Chris@90 457 bool
Chris@95 458 MatrixFileCache::seekTo(size_t x, size_t y)
Chris@87 459 {
Chris@87 460 off_t off = m_headerSize + (x * m_height + y) * sizeof(float);
Chris@90 461
Chris@87 462 if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
Chris@87 463 ::perror("Seek failed");
Chris@87 464 std::cerr << "ERROR: MatrixFileCache::seekTo(" << x << ", " << y
Chris@87 465 << ") failed" << std::endl;
Chris@87 466 return false;
Chris@87 467 }
Chris@87 468
Chris@87 469 return true;
Chris@87 470 }
Chris@87 471