changeset 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 5b8392e80ed6
children 1aebdc68ec6d
files base/FileReadThread.cpp base/FileReadThread.h base/MatrixFileCache.cpp base/MatrixFileCache.h
diffstat 4 files changed, 555 insertions(+), 198 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/FileReadThread.cpp	Thu May 04 13:59:57 2006 +0000
@@ -0,0 +1,238 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "FileReadThread.h"
+
+#include <iostream>
+
+FileReadThread::FileReadThread() :
+    m_nextToken(0),
+    m_exiting(false)
+{
+}
+
+void
+FileReadThread::run()
+{
+    m_mutex.lock();
+
+    while (!m_exiting) {
+        if (m_queue.empty()) {
+            m_condition.wait(&m_mutex, 1000);
+        } else {
+            process();
+            notifyCancelled();
+        }
+    }
+
+    notifyCancelled();
+    m_mutex.unlock();
+
+    std::cerr << "FileReadThread::run() exiting" << std::endl;
+}
+
+void
+FileReadThread::finish()
+{
+    std::cerr << "FileReadThread::finish()" << std::endl;
+
+    m_mutex.lock();
+    while (!m_queue.empty()) {
+        m_cancelledRequests[m_queue.begin()->first] = m_queue.begin()->second;
+        m_newlyCancelled.insert(m_queue.begin()->first);
+        m_queue.erase(m_queue.begin());
+    }
+
+    m_exiting = true;
+    m_mutex.unlock();
+
+    m_condition.wakeAll();
+
+    std::cerr << "FileReadThread::finish() exiting" << std::endl;
+}
+
+int
+FileReadThread::request(const Request &request)
+{
+    m_mutex.lock();
+    
+    int token = m_nextToken++;
+    m_queue[token] = request;
+
+    m_mutex.unlock();
+    m_condition.wakeAll();
+
+    return token;
+}
+
+void
+FileReadThread::cancel(int token)
+{
+    m_mutex.lock();
+
+    if (m_queue.find(token) != m_queue.end()) {
+        m_cancelledRequests[token] = m_queue[token];
+        m_queue.erase(token);
+        m_newlyCancelled.insert(token);
+    }
+
+    m_mutex.unlock();
+}
+
+bool
+FileReadThread::isReady(int token)
+{
+    m_mutex.lock();
+
+    bool ready = m_readyRequests.find(token) != m_readyRequests.end();
+
+    m_mutex.unlock();
+    return ready;
+}
+
+bool
+FileReadThread::isCancelled(int token)
+{
+    m_mutex.lock();
+
+    bool cancelled = 
+        m_cancelledRequests.find(token) != m_cancelledRequests.end() &&
+        m_newlyCancelled.find(token) == m_newlyCancelled.end();
+
+    m_mutex.unlock();
+    return cancelled;
+}
+
+bool
+FileReadThread::getRequest(int token, Request &request)
+{
+    m_mutex.lock();
+
+    bool found = false;
+
+    if (m_queue.find(token) != m_queue.end()) {
+        request = m_queue[token];
+        found = true;
+    } else if (m_cancelledRequests.find(token) != m_cancelledRequests.end()) {
+        request = m_cancelledRequests[token];
+        found = true;
+    } else if (m_readyRequests.find(token) != m_readyRequests.end()) {
+        request = m_readyRequests[token];
+        found = true;
+    }
+
+    m_mutex.unlock();
+    
+    return found;
+}
+
+void
+FileReadThread::done(int token)
+{
+    m_mutex.lock();
+
+    bool found = false;
+
+    if (m_cancelledRequests.find(token) != m_cancelledRequests.end()) {
+        m_cancelledRequests.erase(token);
+        m_newlyCancelled.erase(token);
+        found = true;
+    } else if (m_readyRequests.find(token) != m_readyRequests.end()) {
+        m_readyRequests.erase(token);
+        found = true;
+    } else if (m_queue.find(token) != m_queue.end()) {
+        std::cerr << "WARNING: FileReadThread::done(" << token << "): request is still in queue (wait or cancel it)" << std::endl;
+    }
+
+    m_mutex.unlock();
+
+    if (!found) {
+        std::cerr << "WARNING: FileReadThread::done(" << token << "): request not found" << std::endl;
+    }
+}
+
+void
+FileReadThread::process()
+{
+    // entered with m_mutex locked and m_queue non-empty
+
+    int token = m_queue.begin()->first;
+    Request request = m_queue.begin()->second;
+
+    m_mutex.unlock();
+
+    std::cerr << "FileReadThread::process: got something to do" << std::endl;
+
+    bool successful = false;
+    bool seekFailed = false;
+    ssize_t r = 0;
+
+    if (request.mutex) request.mutex->lock();
+
+    if (::lseek(request.fd, request.start, SEEK_SET) == (off_t)-1) {
+        seekFailed = true;
+    } else {
+        r = ::read(request.fd, request.data, request.size);
+    }
+
+    if (request.mutex) request.mutex->unlock();
+
+    if (seekFailed) {
+        ::perror("Seek failed");
+        std::cerr << "ERROR: FileReadThread::process: seek to "
+                  << request.start << " failed" << std::endl;
+        request.size = 0;
+    } else {
+        if (r < 0) {
+            ::perror("ERROR: FileReadThread::process: Read failed");
+            request.size = 0;
+        } else if (r < ssize_t(request.size)) {
+            std::cerr << "WARNING: FileReadThread::process: read "
+                      << request.size << " returned only " << r << " bytes"
+                      << std::endl;
+            request.size = r;
+        } else {
+            successful = true;
+        }
+    }
+        
+    // Check that the token hasn't been cancelled and the thread
+    // hasn't been asked to finish
+    
+    m_mutex.lock();
+        
+    if (m_queue.find(token) != m_queue.end() && !m_exiting) {
+        m_queue.erase(token);
+        m_readyRequests[token] = request;
+        m_mutex.unlock();
+        std::cerr << "emitting" << std::endl;
+        emit ready(token, successful);
+        m_mutex.lock();
+    }
+}
+
+void
+FileReadThread::notifyCancelled()
+{
+    // entered with m_mutex locked
+
+    while (!m_newlyCancelled.empty()) {
+        int token = *m_newlyCancelled.begin();
+        m_newlyCancelled.erase(token);
+        emit cancelled(token);
+    }
+}
+        
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/FileReadThread.h	Thu May 04 13:59:57 2006 +0000
@@ -0,0 +1,75 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _FILE_READ_THREAD_H_
+#define _FILE_READ_THREAD_H_
+
+#include <QThread>
+#include <QMutex>
+#include <QWaitCondition>
+
+#include <map>
+#include <set>
+
+#include <stdint.h>
+
+class FileReadThread : public QThread
+{
+    Q_OBJECT
+
+public:
+    FileReadThread();
+
+    virtual void run();
+    virtual void finish();
+
+    struct Request {
+        int fd;
+        QMutex *mutex; // used to synchronise access to fd; may be null
+        off_t start;
+        size_t size;
+        char *data; // caller is responsible for allocating and deallocating
+    };
+    
+    virtual int request(const Request &request);
+    virtual void cancel(int token);
+
+    virtual bool isReady(int token);
+    virtual bool isCancelled(int token); // and safe to delete
+    virtual bool getRequest(int token, Request &request);
+    virtual void done(int token);
+
+signals:
+    void ready(int token, bool successful); 
+    void cancelled(int token);
+    
+protected:
+    int m_nextToken;
+    bool m_exiting;
+    
+    typedef std::map<int, Request> RequestQueue;
+    RequestQueue m_queue;
+    RequestQueue m_cancelledRequests;
+    RequestQueue m_readyRequests;
+    std::set<int> m_newlyCancelled;
+
+    QMutex m_mutex;
+    QWaitCondition m_condition;
+
+    void process();
+    void notifyCancelled();
+};
+
+#endif
--- a/base/MatrixFileCache.cpp	Wed May 03 16:48:03 2006 +0000
+++ b/base/MatrixFileCache.cpp	Thu May 04 13:59:57 2006 +0000
@@ -27,33 +27,22 @@
 #include <cstdio>
 
 #include <QFileInfo>
-#include <QFile>
 #include <QDir>
 
 std::map<QString, int> MatrixFileCache::m_refcount;
 QMutex MatrixFileCache::m_refcountMutex;
 
-//!!! This class is a work in progress -- it does only as much as we
-// need for the current SpectrogramLayer.  Slated for substantial
-// refactoring and extension.
-
 MatrixFileCache::MatrixFileCache(QString fileBase, Mode mode) :
     m_fd(-1),
     m_mode(mode),
     m_width(0),
     m_height(0),
     m_headerSize(2 * sizeof(size_t)),
-    m_autoRegionWidth(256),
-    m_off(-1),
-    m_rx(0),
-    m_rw(0),
-    m_userRegion(false),
-    m_region(0)
+    m_defaultCacheWidth(2048),
+    m_prevX(0),
+    m_requestToken(-1)
 {
-    // Ensure header size is a multiple of the size of our data (for
-    // alignment purposes)
-    size_t hs = ((m_headerSize / sizeof(float)) * sizeof(float));
-    if (hs != m_headerSize) m_headerSize = hs + sizeof(float);
+    m_cache.data = 0;
 
     QDir tempDir(TempDirectory::instance()->getPath());
     QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
@@ -101,6 +90,13 @@
     }
 
     m_fileName = fileName;
+    
+    //!!! why isn't this signal being delivered?
+    connect(&m_readThread, SIGNAL(cancelled(int)), 
+            this, SLOT(requestCancelled(int)));
+
+    m_readThread.start();
+
     QMutexLocker locker(&m_refcountMutex);
     ++m_refcount[fileName];
 
@@ -110,10 +106,21 @@
 
 MatrixFileCache::~MatrixFileCache()
 {
-    if (m_rw > 0) {
-        delete[] m_region;
+    float *requestData = 0;
+
+    if (m_requestToken >= 0) {
+        FileReadThread::Request request;
+        if (m_readThread.getRequest(m_requestToken, request)) {
+            requestData = (float *)request.data;
+        }
     }
 
+    m_readThread.finish();
+    m_readThread.wait();
+
+    if (requestData) delete[] requestData;
+    if (m_cache.data) delete[] m_cache.data;
+
     if (m_fd >= 0) {
         if (::close(m_fd) < 0) {
             ::perror("MatrixFileCache::~MatrixFileCache: close failed");
@@ -123,7 +130,8 @@
     if (m_fileName != "") {
         QMutexLocker locker(&m_refcountMutex);
         if (--m_refcount[m_fileName] == 0) {
-            if (!QFile(m_fileName).remove()) {
+            if (::unlink(m_fileName.toLocal8Bit())) {
+                ::perror("Unlink failed");
                 std::cerr << "WARNING: MatrixFileCache::~MatrixFileCache: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl;
             } else {
                 std::cerr << "deleted " << m_fileName.toStdString() << std::endl;
@@ -153,31 +161,26 @@
         return;
     }
 
+    QMutexLocker locker(&m_fdMutex);
+    
     off_t off = m_headerSize + (w * h * sizeof(float));
 
     if (w * h > m_width * m_height) {
 
-/*!!!
-        // If we're going to mmap the file, we need to ensure it's long
-        // enough beforehand
+        if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) {
+            ::perror("Seek failed");
+            std::cerr << "ERROR: MatrixFileCache::resize(" << w << ", "
+                      << h << "): seek failed, cannot resize" << std::endl;
+            return;
+        }
+            
+        // guess this requires efficient support for sparse files
         
-        if (m_preferMmap) {
-        
-            if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) {
-                ::perror("Seek failed");
-                std::cerr << "ERROR: MatrixFileCache::resize(" << w << ", "
-                          << h << "): seek failed, cannot resize" << std::endl;
-                return;
-            }
-            
-            // guess this requires efficient support for sparse files
-            
-            float f(0);
-            if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) {
-                ::perror("WARNING: MatrixFileCache::resize: write failed");
-            }
+        float f(0);
+        if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) {
+            ::perror("WARNING: MatrixFileCache::resize: write failed");
         }
-*/
+
     } else {
         
         if (::ftruncate(m_fd, off) < 0) {
@@ -187,7 +190,6 @@
 
     m_width = 0;
     m_height = 0;
-    m_off = 0;
 
     if (::lseek(m_fd, 0, SEEK_SET) == (off_t)-1) {
         ::perror("ERROR: MatrixFileCache::resize: Seek to write header failed");
@@ -217,6 +219,8 @@
         return;
     }
     
+    QMutexLocker locker(&m_fdMutex);
+
     float *emptyCol = new float[m_height];
     for (size_t y = 0; y < m_height; ++y) emptyCol[y] = 0.f;
 
@@ -226,74 +230,89 @@
     delete[] emptyCol;
 }
 
-void
-MatrixFileCache::setRegionOfInterest(size_t x, size_t width)
+float
+MatrixFileCache::getValueAt(size_t x, size_t y)
 {
-    setRegion(x, width, true);
-}
+    float value = 0.f;
+    if (getValuesFromCache(x, y, 1, &value)) return value;
 
-void
-MatrixFileCache::clearRegionOfInterest()
-{
-    m_userRegion = false;
-}
+    ssize_t r = 0;
 
-float
-MatrixFileCache::getValueAt(size_t x, size_t y) const
-{
-    if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) {
-        float *rp = getRegionPtr(x, y);
-        if (rp) return *rp;
-    } else if (!m_userRegion) {
-        if (autoSetRegion(x)) {
-            float *rp = getRegionPtr(x, y);
-            if (rp) return *rp;
-            else return 0.f;
-        }
+//    std::cout << "MatrixFileCache::getValueAt(" << x << ", " << y << ")"
+//              << ": reading the slow way" << std::endl;
+
+    m_fdMutex.lock();
+
+    if (seekTo(x, y)) {
+        r = ::read(m_fd, &value, sizeof(float));
     }
 
-    if (!seekTo(x, y)) return 0.f;
-    float value;
-    ssize_t r = ::read(m_fd, &value, sizeof(float));
+    m_fdMutex.unlock();
+
     if (r < 0) {
         ::perror("MatrixFileCache::getValueAt: Read failed");
     }
     if (r != sizeof(float)) {
         value = 0.f;
     }
-    if (r > 0) m_off += r;
+
     return value;
 }
 
 void
-MatrixFileCache::getColumnAt(size_t x, float *values) const
+MatrixFileCache::getColumnAt(size_t x, float *values)
 {
-    if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) {
-        float *rp = getRegionPtr(x, 0);
-        if (rp) {
-            for (size_t y = 0; y < m_height; ++y) {
-                values[y] = rp[y];
-            }
-        }
-        return;
-    } else if (!m_userRegion) {
-        if (autoSetRegion(x)) {
-            float *rp = getRegionPtr(x, 0);
-            if (rp) {
-                for (size_t y = 0; y < m_height; ++y) {
-                    values[y] = rp[y];
-                }
-                return;
-            }
-        }
+    if (getValuesFromCache(x, 0, m_height, values)) return;
+
+    ssize_t r = 0;
+
+    std::cout << "MatrixFileCache::getColumnAt(" << x << ")"
+              << ": reading the slow way" << std::endl;
+
+    m_fdMutex.lock();
+
+    if (seekTo(x, 0)) {
+        r = ::read(m_fd, values, m_height * sizeof(float));
     }
 
-    if (!seekTo(x, 0)) return;
-    ssize_t r = ::read(m_fd, values, m_height * sizeof(float));
+    m_fdMutex.unlock();
+    
     if (r < 0) {
         ::perror("MatrixFileCache::getColumnAt: read failed");
     }
-    if (r > 0) m_off += r;
+}
+
+bool
+MatrixFileCache::getValuesFromCache(size_t x, size_t ystart, size_t ycount,
+                                    float *values)
+{
+    m_cacheMutex.lock();
+
+    if (!m_cache.data || x < m_cache.x || x >= m_cache.x + m_cache.width) {
+        bool left = (m_cache.data && x < m_cache.x);
+        m_cacheMutex.unlock();
+        primeCache(x, left); // this doesn't take effect until a later callback
+        m_prevX = x;
+        return false;
+    }
+
+    for (size_t y = ystart; y < ystart + ycount; ++y) {
+        values[y - ystart] = m_cache.data[(x - m_cache.x) * m_height + y];
+    }
+    m_cacheMutex.unlock();
+
+    if (m_cache.x > 0 && x < m_prevX && x < m_cache.x + m_cache.width/4) {
+        primeCache(x, true);
+    }
+
+    if (m_cache.x + m_cache.width < m_width &&
+        x > m_prevX &&
+        x > m_cache.x + (m_cache.width * 3) / 4) {
+        primeCache(x, false);
+    }
+
+    m_prevX = x;
+    return true;
 }
 
 void
@@ -305,14 +324,24 @@
         return;
     }
 
-    if (!seekTo(x, y)) return;
-    ssize_t w = ::write(m_fd, &value, sizeof(float));
-    if (w != sizeof(float)) {
+    ssize_t w = 0;
+    bool seekFailed = false;
+
+    m_fdMutex.lock();
+
+    if (seekTo(x, y)) {
+        w = ::write(m_fd, &value, sizeof(float));
+    } else {
+        seekFailed = true;
+    }
+
+    m_fdMutex.unlock();
+
+    if (!seekFailed && w != sizeof(float)) {
         ::perror("WARNING: MatrixFileCache::setValueAt: write failed");
     }
-    if (w > 0) m_off += w;
 
-    //... update region as appropriate
+    //... update cache as appropriate
 }
 
 void
@@ -324,106 +353,111 @@
         return;
     }
 
-    if (!seekTo(x, 0)) return;
-    ssize_t w = ::write(m_fd, values, m_height * sizeof(float));
-    if (w != ssize_t(m_height * sizeof(float))) {
+    ssize_t w = 0;
+    bool seekFailed = false;
+
+    m_fdMutex.lock();
+
+    if (seekTo(x, 0)) {
+        w = ::write(m_fd, values, m_height * sizeof(float));
+    } else {
+        seekFailed = true;
+    }
+
+    m_fdMutex.unlock();
+
+    if (!seekFailed && w != ssize_t(m_height * sizeof(float))) {
         ::perror("WARNING: MatrixFileCache::setColumnAt: write failed");
     }
-    if (w > 0) m_off += w;
 
-    //... update region as appropriate
+    //... update cache as appropriate
 }
 
-float *
-MatrixFileCache::getRegionPtr(size_t x, size_t y) const
+void
+MatrixFileCache::primeCache(size_t x, bool goingLeft)
 {
-    if (m_rw == 0) return 0;
+//    std::cerr << "MatrixFileCache::primeCache(" << x << ", " << goingLeft << ")" << std::endl;
 
-    float *region = m_region;
+    size_t rx = x;
+    size_t rw = m_defaultCacheWidth;
 
-    float *ptr = &(region[(x - m_rx) * m_height + y]);
-    
-//    std::cerr << "getRegionPtr(" << x << "," << y << "): region is " << m_region << ", returning " << ptr << std::endl;
-    return ptr;
+    size_t left = rw / 3;
+    if (goingLeft) left = (rw * 2) / 3;
+
+    if (rx > left) rx -= left;
+    else rx = 0;
+
+    if (rx + rw > m_width) rw = m_width - rx;
+
+    QMutexLocker locker(&m_cacheMutex);
+
+    if (m_requestToken >= 0) {
+
+        if (x >= m_requestingX &&
+            x <  m_requestingX + m_requestingWidth) {
+
+            if (m_readThread.isReady(m_requestToken)) {
+
+                std::cerr << "last request is ready! (" << m_requestingX << ", "<< m_requestingWidth << ")"  << std::endl;
+
+                FileReadThread::Request request;
+                if (m_readThread.getRequest(m_requestToken, request)) {
+
+                    m_cache.x = (request.start - m_headerSize) / (m_height * sizeof(float));
+                    m_cache.width = request.size / (m_height * sizeof(float));
+                    
+                    std::cerr << "actual: " << m_cache.x << ", " << m_cache.width << std::endl;
+
+                    if (m_cache.data) delete[] m_cache.data;
+                    m_cache.data = (float *)request.data;
+                }
+                
+                m_readThread.done(m_requestToken);
+                m_requestToken = -1;
+            }
+
+            return;
+        }
+
+        std::cerr << "cancelling last request" << std::endl;
+        m_readThread.cancel(m_requestToken);
+//!!!
+        m_requestToken = -1;
+    }
+
+    FileReadThread::Request request;
+    request.fd = m_fd;
+    request.mutex = &m_fdMutex;
+    request.start = m_headerSize + rx * m_height * sizeof(float);
+    request.size = rw * m_height * sizeof(float);
+    request.data = (char *)(new float[rw * m_height]);
+
+    m_requestingX = rx;
+    m_requestingWidth = rw;
+
+    int token = m_readThread.request(request);
+    std::cerr << "MatrixFileCache::primeCache: request token is "
+              << token << std::endl;
+
+    m_requestToken = token;
+}
+
+void
+MatrixFileCache::requestCancelled(int token)
+{
+    std::cerr << "MatrixFileCache::requestCancelled(" << token << ")" << std::endl;
+
+    FileReadThread::Request request;
+    if (m_readThread.getRequest(token, request)) {
+        delete[] ((float *)request.data);
+        m_readThread.done(token);
+    }
 }
 
 bool
-MatrixFileCache::autoSetRegion(size_t x) const
-{
-    size_t rx = x;
-    size_t rw = m_autoRegionWidth;
-    size_t left = rw / 4;
-    if (x < m_rx) left = (rw * 3) / 4;
-    if (rx > left) rx -= left;
-    else rx = 0;
-    if (rx + rw > m_width) rw = m_width - rx;
-    return setRegion(rx, rw, false);
-}
-
-bool
-MatrixFileCache::setRegion(size_t x, size_t width, bool user) const
-{
-    if (!user && m_userRegion) return false;
-    if (m_rw > 0 && x >= m_rx && x + width <= m_rx + m_rw) return true;
-
-    if (m_rw > 0) {
-        delete[] m_region;
-        m_region = 0;
-        m_rw = 0;
-    }
-
-    if (width == 0) {
-        return true;
-    }
-
-    if (!seekTo(x, 0)) return false;
-
-    m_region = new float[width * m_height];
-    MUNLOCK(m_region, width * m_height * sizeof(float));
-
-    ssize_t r = ::read(m_fd, m_region, width * m_height * sizeof(float));
-    if (r < 0) {
-        ::perror("Read failed");
-        std::cerr << "ERROR: MatrixFileCache::setRegion(" << x << ", " << width
-                  << ") failed" << std::endl;
-        delete[] m_region;
-        m_region = 0;
-        return false;
-    }
-    
-    m_off += r;
-
-    if (r < ssize_t(width * m_height * sizeof(float))) {
-        // didn't manage to read the whole thing, but did get something
-        std::cerr << "WARNING: MatrixFileCache::setRegion(" << x << ", " << width
-                  << "): ";
-        width = r / (m_height * sizeof(float));
-        std::cerr << "Only got " << width << " columns" << std::endl;
-    }
-
-    m_rx = x;
-    m_rw = width;
-    if (m_rw == 0) {
-        delete[] m_region;
-        m_region = 0;
-    }
-
-    std::cerr << "MatrixFileCache::setRegion: set region to " << x << ", " << width << std::endl;
-
-    if (user) m_userRegion = true;
-    return true;
-}
-
-bool
-MatrixFileCache::seekTo(size_t x, size_t y) const
+MatrixFileCache::seekTo(size_t x, size_t y)
 {
     off_t off = m_headerSize + (x * m_height + y) * sizeof(float);
-    if (off == m_off) return true;
-
-    if (m_mode == ReadWrite) {
-        std::cerr << "writer: ";
-        std::cerr << "seek required (from " << m_off << " to " << off << ")" << std::endl;
-    }
 
     if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
         ::perror("Seek failed");
@@ -432,7 +466,6 @@
         return false;
     }
 
-    m_off = off;
     return true;
 }
 
--- a/base/MatrixFileCache.h	Wed May 03 16:48:03 2006 +0000
+++ b/base/MatrixFileCache.h	Thu May 04 13:59:57 2006 +0000
@@ -21,12 +21,12 @@
 #include <QMutex>
 #include <map>
 
-// This class is _not_ thread safe.  Each instance must only be used
-// within a single thread.  You may however have as many instances as
-// you like referring to the same file in separate threads.
+#include "FileReadThread.h"
 
-class MatrixFileCache
+class MatrixFileCache : public QObject
 {
+    Q_OBJECT
+
 public:
     enum Mode { ReadOnly, ReadWrite };
 
@@ -39,38 +39,49 @@
     void resize(size_t width, size_t height);
     void reset();
 
-    void setRegionOfInterest(size_t x, size_t width);
-    void clearRegionOfInterest();
-
-    float getValueAt(size_t x, size_t y) const;
-    void getColumnAt(size_t x, float *values) const;
+    float getValueAt(size_t x, size_t y);
+    void getColumnAt(size_t x, float *values);
 
     void setValueAt(size_t x, size_t y, float value);
     void setColumnAt(size_t x, float *values);
-    
+
+protected slots:
+    void requestCancelled(int token);
+
 protected:
     int     m_fd;
     Mode    m_mode;
     size_t  m_width;
     size_t  m_height;
     size_t  m_headerSize;
-    size_t  m_autoRegionWidth;
     QString m_fileName;
+    size_t  m_defaultCacheWidth;
+    size_t  m_prevX;
 
-    mutable off_t   m_off;
-    mutable size_t  m_rx;
-    mutable size_t  m_rw;
-    mutable bool    m_userRegion;
-    mutable float  *m_region;
-    float *getRegionPtr(size_t x, size_t y) const;
+    struct Cache {
+        size_t  x;
+        size_t  width;
+        float  *data;
+    };
 
-    bool autoSetRegion(size_t x) const;
-    bool setRegion(size_t x, size_t width, bool user) const;
+    Cache m_cache;
 
-    bool seekTo(size_t x, size_t y) const;
+    bool getValuesFromCache(size_t x, size_t ystart, size_t ycount,
+                            float *values);
+
+    void primeCache(size_t x, bool left);
+
+    bool seekTo(size_t x, size_t y);
+
+    FileReadThread m_readThread;
+    int m_requestToken;
+    size_t m_requestingX;
+    size_t m_requestingWidth;
 
     static std::map<QString, int> m_refcount;
     static QMutex m_refcountMutex;
+    QMutex m_fdMutex;
+    QMutex m_cacheMutex;
 };
 
 #endif