Blob Blame Raw


#include "tsystem.h"
#include "tcachedlevel.h"

#include "tcodec.h"

#include "texception.h"
//#include "tcachedlevel.h"
//#include "tcodec.h"

#include "tconvert.h"

#ifdef LINUX
#include "texception.h"
//#include "tsystem.h"
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif

#ifdef MACOSX
#include "sys/mman.h"
#include "sys/errno.h"
#endif
//------------------------------------------------------------------------------

#define DWORDLONG_LO_DWORD(dwl64) ((DWORD)(dwl64))
#define DWORDLONG_HI_DWORD(dwl64) ((DWORD)(dwl64 >> 32))

//==============================================================================
//==============================================================================
//==============================================================================
//              TDiskCachePersist
//------------------------------------------------------------------------------

class ImpPD
{
public:
	ImpPD(const TFilePath &fn)
		: m_fname(fn), m_chunkSize(0), m_currentFileSize(0), m_fileMapAddress(0), m_mapOffset(0)
	{
		TFileStatus fileStatus(fn);
		if (fileStatus.doesExist())
			m_currentFileSize = fileStatus.getSize();
		else
			m_currentFileSize = ImpPD::m_defaultFileSize;
	};
	virtual ~ImpPD() {}
	virtual void openFile(const TFilePath &, TINT64 fileSize) = 0;
	virtual void setCurrentView(int pos, int &newLowPos, int &newHiPos) = 0;
	TFilePath m_fname;

	TINT64 m_chunkSize;
	TINT64 m_currentFileSize;
	void *m_fileMapAddress;
	TINT64 m_mapOffset;

	// quantita' espresse in byte
	static TINT64 m_defaultFileSize;
	TUINT32 m_viewSize;
	TINT64 m_reallocSize;
};

TINT64 ImpPD::m_defaultFileSize(100 * 1024 * 1024);

//------------------------------------------------------------------------------

class TDiskCachePersist::Imp
{
public:
	Imp(const TFilePath &fp);
	~Imp();

	bool put(int frame, UCHAR *data, TUINT32 dataSize);
	UCHAR *get(int pos, TUINT32 *size);
	void openFile(const TFilePath &fp, TINT64 fileSize);
	void setCurrentView(int frame)
	{
		if (!m_force && ((m_lowFrame <= frame) && (frame < m_hiFrame)))
			return; //la vista corrente gia' copre il frame
		m_force = false;
		m_impPD->setCurrentView(frame, m_lowFrame, m_hiFrame);
	}

	ImpPD *m_impPD;

	int m_lowFrame, m_hiFrame;
	TThread::Mutex m_mutex;

	bool m_force;
};

#ifdef WIN32
class ImpPDW : public ImpPD
{
private:
	string getLastErrorMessage()
	{
		LPVOID lpMsgBuf;

		DWORD err = GetLastError();
		FormatMessage(
			FORMAT_MESSAGE_ALLOCATE_BUFFER |
				FORMAT_MESSAGE_FROM_SYSTEM |
				FORMAT_MESSAGE_IGNORE_INSERTS,
			NULL,
			err,
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
			(LPTSTR)&lpMsgBuf,
			0,
			NULL);

		string msg((LPCTSTR)lpMsgBuf);

		// Free the buffer.
		LocalFree(lpMsgBuf);
		return msg;
	}

public:
	ImpPDW(const TFilePath &fp);
	~ImpPDW();
	void openFile(const TFilePath &fname, TINT64 fileSize);
	void setCurrentView(int pos, int &newLowPos, int &newHiPos);

private:
	HANDLE m_hFile;
	HANDLE m_hMap;

	SYSTEM_INFO m_systemInfo;
};

#else

class ImpPDX : public ImpPD
{
private:
	class TCachedLevelException : public TException
	{
		static string msgFromErrorCode(int errorCode)
		{
			switch (errorCode) {
			case EBADF:
				return " fd is not a valid file descriptor  (and  MAP_ANONY­MOUS was not set).";
				break;
			/*
    case EACCES_MAP_PRIVATE:
      return "Map private was requested, but fd is not open for reading. Or MAP_SHARED was requested and PROT_WRITE is set, but fd is not open in read/write O_RDWR) mode.";
    break;
*/
			case EINVAL:
				return "We don't like start or length  or  offset.   (E.g., they  are  too  large, or not aligned on a PAGESIZE boundary.)";
				break;

			case ETXTBSY:
				return "MAP_DENYWRITE was set but the object  specified  by fd is open for writing.";
				break;

			case EAGAIN:
				return "The  file  has  been locked, or too much memory has been locked.";
				break;

			case ENOMEM:
				return "No memory is available.";
				break;

			default:
				char *sysErr = strerror(errorCode);
				ostrstream os;
				os << errorCode << '\0';
				os.freeze(false);
				return string(sysErr) + "(" + os.str() + ")";
				break;
			}
			return "";
		}

	public:
		TCachedLevelException(int errorCode)
			: TException(msgFromErrorCode(errorCode))
		{
		}
		~TCachedLevelException() {}
	};

public:
	ImpPDX(const TFilePath &fp);
	~ImpPDX();
	void openFile(const TFilePath &fname, TINT64 fileSize);
	void setCurrentView(int pos, int &newLowPos, int &newHiPos);

private:
	int m_fd;
	size_t m_pageSize;
};

#endif

//  HIGH
//------------------------------------------------------------------------------

TDiskCachePersist::TDiskCachePersist(TRasterCodec *codec, const TFilePath &fp)
	: TCachePersist(codec), m_imp(new Imp(fp))
{
}

//------------------------------------------------------------------------------

TDiskCachePersist::~TDiskCachePersist()
{
	delete m_imp;
}

//------------------------------------------------------------------------------

void TDiskCachePersist::setFrameSize(int lx, int ly, int bpp)
{
	m_imp->m_impPD->m_chunkSize = lx * ly * (bpp >> 3) + m_codec->getHeaderSize();
	m_imp->m_force = true;
}

//------------------------------------------------------------------------------

TRasterP TDiskCachePersist::doGetRaster(int frame)
{
	TRasterP rasP;
	TUINT32 size;
	UCHAR *src = m_imp->get(frame, &size);
	m_codec->decompress(src, size, rasP);
	delete[] src;
	return rasP;
}

//------------------------------------------------------------------------------

bool TDiskCachePersist::doGetRaster(int frame, TRaster32P &ras) const
{
	assert(false);
	return false;
}

//------------------------------------------------------------------------------

bool TDiskCachePersist::doPutRaster(int frame, const TRasterP &ras)
{
	UCHAR *outData = 0;
	TINT32 outDataSize = 0;
	m_codec->compress(ras, 1, &outData, outDataSize);
	bool cached = m_imp->put(frame, outData, outDataSize);
	delete[] outData;
	return cached;
}

//------------------------------------------------------------------------------

UCHAR *TDiskCachePersist::getRawData(int frame, TINT32 &size, int &lx, int &ly)
{
	TUINT32 inDataSize;
	UCHAR *src = m_imp->get(frame, &inDataSize);
	return m_codec->removeHeader(src, inDataSize, size, lx, ly);
}

//------------------------------------------------------------------------------
// MEDIUM
TDiskCachePersist::Imp::Imp(const TFilePath &fp)
	: m_impPD(0)
{
#ifdef WIN32
	m_impPD = new ImpPDW(fp);
#else
	m_impPD = new ImpPDX(fp);
#endif
	m_impPD->m_currentFileSize = TFileStatus(fp).doesExist() ? TFileStatus(fp).getSize() : 0; // per gli arrotondamenti...
}

//------------------------------------------------------------------------------

TDiskCachePersist::Imp::~Imp()
{
	delete m_impPD;
}
//------------------------------------------------------------------------------
// LOW

#ifdef WIN32
ImpPDW::ImpPDW(const TFilePath &fp)
	: ImpPD(fp), m_hFile(0), m_hMap(0)
{
	GetSystemInfo(&m_systemInfo);

	m_viewSize = 100 * 1024 * 1024;
	m_reallocSize = 250 * 1024 * 1024;

	TINT64 allocUnitCount = m_reallocSize / m_systemInfo.dwAllocationGranularity;

	// rendo m_reallocSize multiplo di m_systemInfo.dwAllocationGranularity
	if ((m_reallocSize % m_systemInfo.dwAllocationGranularity) != 0)
		++allocUnitCount;

	m_reallocSize = allocUnitCount * m_systemInfo.dwAllocationGranularity;

	TINT64 fileSize = m_defaultFileSize;

	TFileStatus fileStatus(fp);
	if (fileStatus.doesExist())
		fileSize = fileStatus.getSize();

	try {
		openFile(fp, fileSize);
	} catch (TException &e) {
		m_currentFileSize = 0;
		throw e;
	}

	m_currentFileSize = fileSize;
}

//------------------------------------------------------------------------------

ImpPDW::~ImpPDW()
{
	if (m_fileMapAddress)
		UnmapViewOfFile(m_fileMapAddress);
	CloseHandle(m_hMap);
	CloseHandle(m_hFile);
}

//------------------------------------------------------------------------------

void ImpPDW::openFile(const TFilePath &fname, TINT64 fileSize)
{
	DWORD dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
	DWORD dwShareMode = 0; // dwShareMode == 0 --> accesso esclusivo

	// lpSecurityAttributes == NULL --> l'handle non puo' essere
	// ereditato da processi figli
	LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL;

	DWORD dwCreationDisposition = OPEN_ALWAYS;				//CREATE_ALWAYS;
	DWORD dwFlagsAndAttributes = FILE_FLAG_SEQUENTIAL_SCAN; //FILE_ATTRIBUTE_NORMAL;//

	HANDLE hTemplateFile = NULL;

	m_hFile = CreateFileW(
		fname.getWideString().c_str(), // file name
		dwDesiredAccess,			   // access mode
		dwShareMode,				   // share mode
		NULL,						   // SD
		dwCreationDisposition,		   // how to create
		dwFlagsAndAttributes,		   // file attributes
		hTemplateFile				   // handle to template file
		);

	if (m_hFile == INVALID_HANDLE_VALUE) {
		string errMsg = getLastErrorMessage();
		throw TException(wstring(L"Unable to open cache file: ") + fname.getWideString() + L"\n" + toWideString(errMsg));
	}

	DWORD flProtect = PAGE_READWRITE;
	DWORD dwMaximumSizeHigh = DWORDLONG_HI_DWORD(fileSize);
	DWORD dwMaximumSizeLow = DWORDLONG_LO_DWORD(fileSize);
	LPCTSTR lpName = NULL; // l'oggetto non ha nome

	m_hMap = CreateFileMapping(
		m_hFile,		   // handle to file
		NULL,			   // security
		flProtect,		   // protection
		dwMaximumSizeHigh, // high-order DWORD of size
		dwMaximumSizeLow,  // low-order DWORD of size
		lpName			   // object name
		);

	if (m_hMap == NULL) {
		string errMsg = getLastErrorMessage();
		CloseHandle(m_hFile);
		m_hFile = 0;
		throw TException("Unable to create file mapping. " + errMsg);
	}
}

//------------------------------------------------------------------------------

void ImpPDW::setCurrentView(int frame, int &newLowFrame, int &newHiFrame)
{
	if (m_fileMapAddress)
		UnmapViewOfFile(m_fileMapAddress);

	newLowFrame = frame;

	newHiFrame = newLowFrame + TINT32(m_viewSize / m_chunkSize);

	DWORD allocGranularity = m_systemInfo.dwAllocationGranularity;
	TINT64 viewOffset = (TINT64(newLowFrame * m_chunkSize) / allocGranularity) * allocGranularity;
	m_mapOffset = newLowFrame * m_chunkSize - viewOffset;

	TINT64 fileSize = newHiFrame * m_chunkSize;

	if ((fileSize > m_currentFileSize) || !m_hMap) // devo riallocare!
	{
		CloseHandle(m_hMap);
		m_hMap = 0;
		CloseHandle(m_hFile);
		m_hFile = 0;

		TINT64 allocUnitCount = fileSize / m_reallocSize;
		// rendo fileSize multiplo di m_reallocSize
		if ((fileSize % m_reallocSize) != 0)
			++allocUnitCount;

		fileSize = allocUnitCount * m_reallocSize;

		openFile(m_fname, fileSize);
		m_currentFileSize = fileSize;
	}

	DWORD dwDesiredAccess = FILE_MAP_WRITE;
	m_fileMapAddress = MapViewOfFile(m_hMap,						 // handle to file-mapping object
									 dwDesiredAccess,				 // access mode: Write permission
									 DWORDLONG_HI_DWORD(viewOffset), // high-order DWORD of offset: Max. object size.
									 DWORDLONG_LO_DWORD(viewOffset), // low-order DWORD of offset: Size of hFile.
									 m_viewSize);					 // number of bytes to map

	if (m_fileMapAddress == NULL) {
		string errMsg = getLastErrorMessage();
		CloseHandle(m_hMap);
		m_hMap = 0;
		CloseHandle(m_hFile);
		m_hFile = 0;

		throw TException("Unable to memory map cache file. " + errMsg);
	}
}
#else

ImpPDX::ImpPDX(const TFilePath &fp)
	: ImpPD(fp), m_fd(-1)
{
	//std::cout << "cache file " << toString(m_fname.getFullPath()) << std::endl;
	m_pageSize = getpagesize();
	openFile(m_fname, 0);
	assert(m_fd >= 0);
}

//------------------------------------------------------------------------------

ImpPDX::~ImpPDX()
{
	if (m_fileMapAddress)
		munmap(m_fileMapAddress, m_viewSize);
	close(m_fd);
	m_fd = 0;
}

//------------------------------------------------------------------------------

void ImpPDX::openFile(const TFilePath &fname, TINT64 fileSize)
{
	assert(0);
	/*
string fn(toString(fname.getWideString()));
std::cout << "open " << fn << std::endl;
m_fd = open(fn.c_str(), O_RDWR|O_CREAT, 00666);
assert(m_fd >=0);
*/
}

void ImpPDX::setCurrentView(int pos, int &newLowPos, int &newHiPos)
{
	newLowPos = pos;
	newHiPos = newLowPos + (m_viewSize / m_chunkSize);

	assert(m_fd >= 0);
	if (m_fileMapAddress) //previous view...
		if (munmap(m_fileMapAddress, m_viewSize) != 0)
			throw TCachedLevelException(errno);
	void *start = 0;
	int flags = MAP_SHARED;
	size_t viewOffset = ((newLowPos * m_chunkSize) / m_pageSize) * m_pageSize;
	m_mapOffset = newLowPos * m_chunkSize - viewOffset;

	assert(!"controllare le dimensioni");
	unsigned long lastByte = (unsigned long)(((newHiPos * m_chunkSize) / (double)m_pageSize + 0.5) * m_pageSize);

	if (lastByte > m_currentFileSize) // devo riallocare!
	{
		unsigned long bu = (unsigned long)((lastByte / (double)m_reallocSize + 0.5) * m_reallocSize);
		bu = (unsigned long)((bu / (double)m_pageSize + 0.5) * m_pageSize);
		//m_maxFileSize = tmax(m_maxFileSize + m_reallocFileSize, lastByte);
		m_currentFileSize += bu;

		std::cout << "new cache size " << m_currentFileSize << std::endl;
		if (lseek(m_fd, m_currentFileSize, SEEK_SET) == -1)
			throw TCachedLevelException(errno);
		if (write(m_fd, "", 1) == -1)
			throw TCachedLevelException(errno);
		if (ftruncate(m_fd, m_currentFileSize) == -1)
			throw TCachedLevelException(errno);
	}

	m_fileMapAddress = mmap(start, m_viewSize, PROT_READ | PROT_WRITE, flags, m_fd, viewOffset);
	if (m_fileMapAddress == (void *)-1)
		throw TCachedLevelException(errno);
}

#endif

//------------------------------------------------------------------------------
#ifndef WIN32
#define ULONGLONG unsigned long long
#endif
bool TDiskCachePersist::Imp::put(int frame, UCHAR *data, TUINT32 dataSize)
{
	if (dataSize != m_impPD->m_chunkSize)
		return false;

	TThread::ScopedLock sl(m_mutex);

	setCurrentView(frame);
	ULONGLONG offset = (frame - m_lowFrame) * m_impPD->m_chunkSize;
	UCHAR *dst = (UCHAR *)m_impPD->m_fileMapAddress + offset;
	memcpy(dst + m_impPD->m_mapOffset, data, dataSize);
	return true;
}

//------------------------------------------------------------------------------

UCHAR *TDiskCachePersist::Imp::get(int pos, TUINT32 *size)
{
	UCHAR *ret = new UCHAR[TINT32(m_impPD->m_chunkSize)];

	TThread::ScopedLock sl(m_mutex);
	setCurrentView(pos);
	ULONGLONG offset = (pos - m_lowFrame) * m_impPD->m_chunkSize;
	UCHAR *src = (UCHAR *)m_impPD->m_fileMapAddress + offset + m_impPD->m_mapOffset;
	memcpy(ret, src, TINT32(m_impPD->m_chunkSize));
	*size = TUINT32(m_impPD->m_chunkSize);
	return ret;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// TRasterCache
//------------------------------------------------------------------------------

class TRasterCache::Data
{
public:
	Data(TCachePersist *cp)
		: m_cp(cp), m_size(0, 0), m_prefetchEnabled(false), m_prefetchedFrame(-1), m_frameToPrefetch(-1), m_preLoader(1, true)
	{
	}
	~Data() {}

	class FrameData
	{
	public:
		bool m_valid;
		//  int m_size; // dimensione in byte del raster codificato
		~FrameData() {}
	};

	bool isFrameCached(int frame) const;

	TDimension m_size; // dimensioni dei raster in cache
	int m_bpp;

	typedef map<int, FrameData> Status;
	Status m_status;
	TCachePersist *m_cp;
	TThread::Mutex m_accessMutex;

	TThread::Executor m_preLoader;
	bool m_prefetchEnabled;
	int m_prefetchedFrame;
	int m_frameToPrefetch;
	TRasterP m_prefetchedRas;
};

bool TRasterCache::Data::isFrameCached(int frame) const
{
	// volutamente senza ScopedLock
	Data::Status::const_iterator it = m_status.find(frame);
	if (it == m_status.end())
		return false;
	Data::FrameData fd = it->second;
	return fd.m_valid;
}

//==============================================================================

namespace
{

class Load : public TThread::Runnable
{
public:
	Load(int frameToPrefetch, TCachePersist *cp, int &prefetchedFrame, TRasterP &prefetchedRas)
		: m_frame(frameToPrefetch), m_cp(cp), m_prefetchedFrame(prefetchedFrame), m_prefetchedRas(prefetchedRas) {}

	void run();

private:
	int m_frame;
	TCachePersist *m_cp;
	int &m_prefetchedFrame;
	TRasterP &m_prefetchedRas;
};

void Load::run()
{
	m_prefetchedRas = m_cp->doGetRaster(m_frame);
	m_prefetchedFrame = m_frame;
}
};

//==============================================================================

TRasterCache::TRasterCache(TCachePersist *cp)
	: m_data(new Data(cp))
{
}

//------------------------------------------------------------------------------

TRasterCache::~TRasterCache()
{
	delete m_data;
}

//------------------------------------------------------------------------------
void TRasterCache::setMode(const TDimension &size, int bpp)
{
	TThread::ScopedLock sl(m_data->m_accessMutex);

	m_data->m_size = size; // dimensioni dei raster in cache
	m_data->m_bpp = bpp;

	m_data->m_cp->setFrameSize(size.lx, size.ly, bpp);
	invalidate();
}

//------------------------------------------------------------------------------

void TRasterCache::getMode(TDimension &size, int &bpp) const
{
	size = m_data->m_size; // dimensioni dei raster in cache
	bpp = m_data->m_bpp;
}

//------------------------------------------------------------------------------

TRasterP TRasterCache::getRaster(int frame) const
{
	TThread::ScopedLock sl(m_data->m_accessMutex);

	if (m_data->m_prefetchEnabled) {
		/*
    if (frame == m_data->m_frameToPrefetch)
      m_data->m_preLoader.wait();
    else
  */
		{
			m_data->m_preLoader.clear();
			//m_data->m_preLoader.cancel();
		}

		TRasterP ras;
		if (frame == m_data->m_prefetchedFrame)
			ras = m_data->m_prefetchedRas;
		else
			ras = m_data->m_cp->doGetRaster(frame);

		if (isFrameCached(frame + 1)) {
			// il frame successivo a quello richiesto e' nella cache
			// -> avvia il prefetch di tale raster
			m_data->m_frameToPrefetch = frame + 1;
			m_data->m_preLoader.addTask(
				new Load(m_data->m_frameToPrefetch, m_data->m_cp,
						 m_data->m_prefetchedFrame, m_data->m_prefetchedRas));
		}

		return ras;
	} else {
		return m_data->m_cp->doGetRaster(frame);
	}
}

//------------------------------------------------------------------------------

bool TRasterCache::getRaster(int frame, TRaster32P &ras) const
{
	TThread::ScopedLock sl(m_data->m_accessMutex);
	if (m_data->isFrameCached(frame)) {
		bool rc = m_data->m_cp->doGetRaster(frame, ras);
		assert(rc);
		return true;
	} else
		return false;
}

//------------------------------------------------------------------------------

void TRasterCache::putRaster(int frame, const TRasterP &ras)
{
	TThread::ScopedLock sl(m_data->m_accessMutex);
	Data::Status::iterator it = m_data->m_status.find(frame);
	bool cached = false;
	try {
		cached = m_data->m_cp->doPutRaster(frame, ras);
	} catch (TException &e) {
		if (it != m_data->m_status.end()) {
			Data::FrameData fd;
			fd.m_valid = false;
			m_data->m_status[frame] = fd;
		}
		throw e;
	}
	if (cached) {
		Data::FrameData fd;
		fd.m_valid = true;
		m_data->m_status[frame] = fd;
	}
}

//------------------------------------------------------------------------------

UCHAR *TRasterCache::getRawData(int frame, TINT32 &size, int &lx, int &ly) const
{
	return m_data->m_cp->getRawData(frame, size, lx, ly);
}

//------------------------------------------------------------------------------

bool TRasterCache::isFrameCached(int frame) const
{
	TThread::ScopedLock sl(m_data->m_accessMutex);
	return m_data->isFrameCached(frame);
}

//------------------------------------------------------------------------------

void TRasterCache::invalidate()
{
	TThread::ScopedLock sl(m_data->m_accessMutex);
	m_data->m_status.clear();
	m_data->m_cp->onInvalidate();
}

//------------------------------------------------------------------------------

void TRasterCache::invalidate(int startFrame, int endFrame)
{
	assert(startFrame <= endFrame);
	TThread::ScopedLock sl(m_data->m_accessMutex);

	Data::Status::iterator low = m_data->m_status.lower_bound(startFrame);
	Data::Status::iterator hi = m_data->m_status.upper_bound(endFrame);

#ifdef _DEBUG
	int count = m_data->m_status.size();

	if (low != m_data->m_status.end() && hi != m_data->m_status.end()) {
		int ll = low->first;
		int hh = hi->first;
		assert(ll <= hh);
	}
#endif

	if (low != m_data->m_status.end()) {
		m_data->m_status.erase(low, hi);
		m_data->m_cp->onInvalidate(startFrame, endFrame);
	}
}

//------------------------------------------------------------------------------

void TRasterCache::enablePrefetch(bool newState)
{
	m_data->m_prefetchEnabled = newState;
}

//------------------------------------------------------------------------------

bool TRasterCache::isPrefetchEnabled() const
{
	return m_data->m_prefetchEnabled;
}

//------------------------------------------------------------------------------

TUINT64 TRasterCache::getUsedSpace()
{
	return m_data->m_cp->getUsedSpace();
}

//==============================================================================
//==============================================================================
//==============================================================================
//              TRamCachePersist
//------------------------------------------------------------------------------
class TRamCachePersist::Imp
{
	friend class TRamCachePersist;

public:
	Imp()
		: m_cacheSize(0), m_chunks()
	{
	}
	~Imp()
	{
		for (CompressedChunks::iterator it = m_chunks.begin(); it != m_chunks.end(); ++it) {
			CompressedChunk *cc = it->second;
			m_cacheSize -= cc->m_size;
			delete cc;
		}
		assert(m_cacheSize == 0); // se m_cacheSize > 0 mi sono perso qualche chunk
								  // se m_cacheSize < 0 ho liberato 2 volte qualche chunk
		m_chunks.clear();
	}

	class CompressedChunk
	{
	public:
		CompressedChunk(UCHAR *buffer, int size) : m_buffer(buffer), m_size(size) {}
		~CompressedChunk()
		{
			delete[] m_buffer;
		}
		UCHAR *m_buffer;
		int m_size;
	};

	typedef map<int, CompressedChunk *> CompressedChunks;
	CompressedChunks m_chunks;
	TUINT64 m_cacheSize;
};

TRamCachePersist::TRamCachePersist(TRasterCodec *codec)
	: TCachePersist(codec), m_imp(new Imp)
{
}

//------------------------------------------------------------------------------

TRamCachePersist::~TRamCachePersist()
{
	delete m_imp;
}

//------------------------------------------------------------------------------

TRasterP TRamCachePersist::doGetRaster(int frame)
//void TRamCachePersist::doGetRaster(int frame, const TRasterP &ras)
{
	Imp::CompressedChunks::const_iterator it = m_imp->m_chunks.find(frame);
	if (it == m_imp->m_chunks.end())
		return TRasterP();
	Imp::CompressedChunk *cc = it->second;
	assert(cc);
	TRasterP rasP;
	m_codec->decompress(cc->m_buffer, cc->m_size, rasP);
	return rasP;
}

//------------------------------------------------------------------------------

bool TRamCachePersist::doGetRaster(int frame, TRaster32P &ras) const
{
	Imp::CompressedChunks::const_iterator it = m_imp->m_chunks.find(frame);
	if (it == m_imp->m_chunks.end())
		return false;
	Imp::CompressedChunk *cc = it->second;
	assert(cc);
	TRasterP rasP(ras);
	m_codec->decompress(cc->m_buffer, cc->m_size, rasP);
	return true;
}

//------------------------------------------------------------------------------

bool TRamCachePersist::doPutRaster(int frame, const TRasterP &ras)
{
	Imp::CompressedChunks::iterator it = m_imp->m_chunks.find(frame);
	if (it != m_imp->m_chunks.end()) {
		m_imp->m_cacheSize -= it->second->m_size;
		delete it->second;
		m_imp->m_chunks.erase(it);
	}

	UCHAR *outData = 0;
	TINT32 outDataSize = 0;
	m_codec->compress(ras, 1, &outData, outDataSize);
	m_imp->m_cacheSize += outDataSize;
	Imp::CompressedChunk *cc = new Imp::CompressedChunk(outData, outDataSize);
	m_imp->m_chunks.insert(Imp::CompressedChunks::value_type(frame, cc));
	return true;
}

//------------------------------------------------------------------------------

void TRamCachePersist::onInvalidate()
{
	for (Imp::CompressedChunks::iterator it = m_imp->m_chunks.begin();
		 it != m_imp->m_chunks.end();
		 ++it) {
		Imp::CompressedChunk *cc = it->second;
		m_imp->m_cacheSize -= cc->m_size;
		delete cc;
	}

	m_imp->m_chunks.clear();
}

//------------------------------------------------------------------------------

void TRamCachePersist::onInvalidate(int startFrame, int endFrame)
{ // ottimizzabile
	assert(startFrame <= endFrame);

	for (int frame = startFrame; frame <= endFrame; ++frame) {
		Imp::CompressedChunks::iterator it = m_imp->m_chunks.find(frame);
		if (it != m_imp->m_chunks.end()) {
			m_imp->m_cacheSize -= it->second->m_size;
			delete it->second;
			m_imp->m_chunks.erase(it);
		}
	}
}

//------------------------------------------------------------------------------

UCHAR *TRamCachePersist::getRawData(int frame, TINT32 &size, int &lx, int &ly)
{
	Imp::CompressedChunks::const_iterator it = m_imp->m_chunks.find(frame);
	if (it == m_imp->m_chunks.end())
		return 0;
	Imp::CompressedChunk *cc = it->second;
	assert(cc);
	return m_codec->removeHeader(cc->m_buffer, cc->m_size, size, lx, ly);
}

//------------------------------------------------------------------------------

TUINT64 TRamCachePersist::getUsedSpace()
{
	return m_imp->m_cacheSize;
}

//------------------------------------------------------------------------------

void TDiskCachePersist::onInvalidate()
{
	//m_imp->m_chunkSize = 0;
}

//------------------------------------------------------------------------------

void TDiskCachePersist::onInvalidate(int startFrame, int endFrame)
{
	//m_imp->m_chunkSize = 0;
}

//------------------------------------------------------------------------------

TUINT64 TDiskCachePersist::getUsedSpace()
{
	assert(0);
	return 0;
}

//==============================================================================
//==============================================================================
//==============================================================================
//
//  TDiskCachePersist2
//
//------------------------------------------------------------------------------
#ifdef WIN32
namespace
{

class ZFile
{
public:
	ZFile(const TFilePath &fp, bool directIO, bool asyncIO);
	~ZFile();

	void open();

	int read(BYTE buf[], int size, TINT64 qwOffset) const;
	int write(BYTE buf[], int size, TINT64 qwOffset) const;

	void waitForAsyncIOCompletion() const;

	TFilePath getFilePath() const
	{
		return m_filepath;
	}

	int getBytesPerSector() const
	{
		return m_bytesPerSector;
	}

	static void CALLBACK FileIOCompletionRoutine(
		DWORD errCode,
		DWORD byteTransferred,
		LPOVERLAPPED overlapped);

private:
	TFilePath m_filepath;
	bool m_directIO;
	bool m_asyncIO;
	DWORD m_bytesPerSector;

	HANDLE m_fileHandle;
	HANDLE m_writeNotPending;
};

//-----------------------------------------------------------------------------

ZFile::ZFile(const TFilePath &fp, bool directIO, bool asyncIO)
	: m_filepath(fp), m_directIO(directIO), m_asyncIO(asyncIO), m_fileHandle(0), m_writeNotPending(0)
{
	DWORD sectorsPerCluster;
	DWORD numberOfFreeClusters;
	DWORD totalNumberOfClusters;

	TFilePathSet disks = TSystem::getDisks();

	TFilePath disk = fp;
	while (std::find(disks.begin(), disks.end(), disk) == disks.end())
		disk = disk.getParentDir();

	BOOL ret = GetDiskFreeSpaceW(
		disk.getWideString().c_str(), // root path
		&sectorsPerCluster,			  // sectors per cluster
		&m_bytesPerSector,			  // bytes per sector
		&numberOfFreeClusters,		  // free clusters
		&totalNumberOfClusters		  // total clusters
		);

	if (m_asyncIO)
		m_writeNotPending = CreateEvent(NULL, TRUE, TRUE, NULL);
}

//-----------------------------------------------------------------------------

ZFile::~ZFile()
{
	if (m_fileHandle)
		CloseHandle(m_fileHandle);

	if (m_writeNotPending)
		CloseHandle(m_writeNotPending);
}

//-----------------------------------------------------------------------------

void ZFile::open()
{
	DWORD flagsAndAttributes = 0;
	flagsAndAttributes = m_directIO ? FILE_FLAG_NO_BUFFERING : 0UL;
	flagsAndAttributes |= m_asyncIO ? FILE_FLAG_OVERLAPPED : 0UL;

	// Open the file for write access.
	m_fileHandle = CreateFileW(m_filepath.getWideString().c_str(),
							   GENERIC_READ | GENERIC_WRITE, // Read/Write access
							   0,							 // no sharing allowed
							   NULL,						 // no security
							   OPEN_ALWAYS,					 // open it or create new if it doesn't exist
							   flagsAndAttributes,
							   NULL); // ignored

	if (m_fileHandle == INVALID_HANDLE_VALUE) {
		m_fileHandle = 0;

		char errorMessage[2048];

		DWORD error = GetLastError();
		FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0UL, error,
					  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorMessage, 2048, NULL);

		throw TException(errorMessage);
	}
}

//-----------------------------------------------------------------------------

int ZFile::read(BYTE buf[], int size, TINT64 qwOffset) const
{
	assert(size % m_bytesPerSector == 0);
	assert(qwOffset % m_bytesPerSector == 0);

	char msg[2048] = "";
	unsigned long bytesToRead; // Padded number of bytes to read.
	unsigned long bytesRead;   // count of bytes actually read

	OVERLAPPED overLapped;
	memset(&overLapped, 0, sizeof(overLapped));

#define DWORDLONG_LO_DWORD(dwl64) ((DWORD)(dwl64))
#define DWORDLONG_HI_DWORD(dwl64) ((DWORD)(dwl64 >> 32))

	// set the overlapped stucture with the offsets
	overLapped.Offset = DWORDLONG_LO_DWORD(qwOffset);
	overLapped.OffsetHigh = DWORDLONG_HI_DWORD(qwOffset);

	if (m_asyncIO) {
		overLapped.hEvent = CreateEvent(
			NULL,  // SD
			TRUE,  // manual reset
			FALSE, // initial state is not signaled
			NULL); // object name
	} else
		overLapped.hEvent = NULL;

	bytesToRead = size;

	// Read a bunch of bytes and store in buf
	int result = ReadFile(
		m_fileHandle, // file handle
		(void *)buf,  // buffer to store data
		bytesToRead,  // num bytes to read
		&bytesRead,   // bytes read
		&overLapped); // stucture for file offsets

	if (!result) {
		DWORD error = GetLastError();
		if (m_asyncIO && ERROR_IO_PENDING == error) {
			if (!GetOverlappedResult(m_fileHandle, &overLapped, &bytesRead, TRUE)) {
				char errorMessage[2048];
				FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0UL, error,
							  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorMessage, 2048, NULL);

				throw TException(errorMessage);
			}
		} else {
			char errorMessage[2048];
			FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0UL, error,
						  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorMessage, 2048, NULL);

			throw TException(errorMessage);
		}
	}

	return bytesRead;
}

//-----------------------------------------------------------------------------

int ZFile::write(BYTE buf[], int size, TINT64 qwOffset) const
{
	assert(size % m_bytesPerSector == 0);
	assert(qwOffset % m_bytesPerSector == 0);

	char msg[2048] = "";
	unsigned long bytesToWrite;		// Padded number of bytes to write.
	unsigned long bytesWritten = 0; // count of bytes actually writtten

	int result;

	if (m_asyncIO) {
		OVERLAPPED *overLapped = new OVERLAPPED;
		memset(overLapped, 0, sizeof(OVERLAPPED));

		// set the overlapped stucture with the offsets
		overLapped->Offset = DWORDLONG_LO_DWORD(qwOffset);
		overLapped->OffsetHigh = DWORDLONG_HI_DWORD(qwOffset);
		overLapped->hEvent = NULL;

		bytesToWrite = size;

		result = WriteFileEx(
			m_fileHandle, // file handle
			(void *)buf,  // data buffer
			bytesToWrite, // num bytes to write
			overLapped,   // stucture for file offsets
			&ZFile::FileIOCompletionRoutine);

		ResetEvent(m_writeNotPending);
	} else {
		OVERLAPPED overLapped;
		memset(&overLapped, 0, sizeof(overLapped));

		// set the overlapped stucture with the offsets
		overLapped.Offset = DWORDLONG_LO_DWORD(qwOffset);
		overLapped.OffsetHigh = DWORDLONG_HI_DWORD(qwOffset);
		overLapped.hEvent = NULL;

		bytesToWrite = size;

		result = WriteFile(
			m_fileHandle,  // file handle
			(void *)buf,   // data buffer
			bytesToWrite,  // num bytes to read
			&bytesWritten, // bytes read
			&overLapped);  // stucture for file offsets
	}

	if (!result) {
		char errorMessage[2048];
		DWORD error = GetLastError();
		FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0UL, error,
					  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorMessage, 2048, NULL);

		throw TException(errorMessage);
	}

	return bytesWritten;
}

//------------------------------------------------------------------------------

void ZFile::waitForAsyncIOCompletion() const
{
	if (m_asyncIO) {
		WaitForSingleObjectEx(m_writeNotPending, INFINITE, TRUE);
		SetEvent(m_writeNotPending);
	}
}

//------------------------------------------------------------------------------

void CALLBACK ZFile::FileIOCompletionRoutine(
	DWORD errCode,
	DWORD byteTransferred,
	LPOVERLAPPED overlapped)
{
	delete overlapped;
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

class BufferQueue
{
public:
	class Item
	{
	public:
		Item(int frame, UCHAR *buffer, int bufferSize, int chunkSize)
			: m_frame(frame), m_buffer(buffer), m_bufferSize(bufferSize), m_chunkSize(chunkSize)
		{
		}

		int m_frame;
		UCHAR *m_buffer;
		int m_bufferSize;
		TINT64 m_chunkSize;
	};

	BufferQueue(int capacity, int allocUnit)
		: m_capacity(capacity), m_allocUnit(allocUnit), m_notEmpty(), m_notFull(), m_mutex(), m_bufferCount(0), m_nextPutItem(0), m_nextGetItem(0)
	{
		for (int i = 0; i < m_capacity; ++i)
			m_items.push_back(Item(-1, (UCHAR *)0, 0, 0));
	}

	~BufferQueue()
	{
		for (int i = 0; i < m_capacity; ++i)
			delete[] m_items[i].m_buffer;
	}

	void put(int frame, UCHAR *buffer, int bufferSize, int chunkSize)
	{
		TThread::ScopedLock sl(m_mutex);
		while (m_bufferCount == m_capacity)
			m_notFull.wait(sl);

		if (m_items[m_nextPutItem].m_chunkSize != chunkSize) {
			delete[] m_items[m_nextPutItem].m_buffer;
			m_items[m_nextPutItem].m_buffer = new UCHAR[chunkSize];
			m_items[m_nextPutItem].m_chunkSize = chunkSize;
		}

		memcpy(m_items[m_nextPutItem].m_buffer, buffer, bufferSize);
		m_items[m_nextPutItem].m_frame = frame;

		m_nextPutItem = (m_nextPutItem + 1) % m_capacity;
		++m_bufferCount;
		m_notEmpty.notifyOne();
	}

	BufferQueue::Item get()
	{
		TThread::ScopedLock sl(m_mutex);

		while (m_bufferCount == 0)
			m_notEmpty.wait(sl);

		m_notFull.notifyOne();

		BufferQueue::Item item = m_items[m_nextGetItem];

		m_nextGetItem = (m_nextGetItem + 1) % m_capacity;
		--m_bufferCount;
		return item;
	}

	void saveOne(ZFile *file)
	{
		TThread::ScopedLock sl(m_mutex);

		while (m_bufferCount == 0)
			m_notEmpty.wait(sl);

		m_notFull.notifyOne();

		BufferQueue::Item item = m_items[m_nextGetItem];

		m_nextGetItem = (m_nextGetItem + 1) % m_capacity;
		--m_bufferCount;

		TINT64 pos = item.m_frame * item.m_chunkSize;
		TINT64 sectorCount = pos / m_allocUnit;

		if ((pos % m_allocUnit) != 0)
			++sectorCount;

		pos = sectorCount * m_allocUnit;
		file->write(item.m_buffer, (TINT32)item.m_chunkSize, pos);
	}

	int size()
	{
		TThread::ScopedLock sl(m_mutex);
		return m_bufferCount;
	}

private:
	int m_capacity;
	int m_allocUnit;

	TThread::Condition m_notEmpty;
	TThread::Condition m_notFull;
	TThread::Mutex m_mutex;

	vector<Item> m_items;

	int m_bufferCount;
	int m_nextPutItem;
	int m_nextGetItem;
};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

class WriteBufferTask : public TThread::Runnable
{
public:
	WriteBufferTask(ZFile *file, BufferQueue *bufferQueue)
		: m_file(file), m_bufferQueue(bufferQueue)
	{
	}

	void run();

	ZFile *m_file;
	BufferQueue *m_bufferQueue;
};

//------------------------------------------------------------------------------

void WriteBufferTask::run()
{
	while (true) {
		TThread::milestone();

		try {
			m_bufferQueue->saveOne(m_file);
			m_file->waitForAsyncIOCompletion();
		} catch (TException & /*e*/) {
		} catch (...) {
		}
	}
}

} // anonymous namespace

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

class TDiskCachePersist2::Imp
{
public:
	Imp(const TFilePath &fp, bool asyncWrite)
		: m_chunkSize(0), m_readBuffer(0), m_file(new ZFile(fp, true, asyncWrite)), m_asyncWrite(asyncWrite), m_executor(0), m_bufferQueue(0)
	{
		m_file->open();
		m_allocUnit = m_file->getBytesPerSector();

		if (m_asyncWrite) {
			m_executor = new TThread::Executor();
			m_bufferQueue = new BufferQueue(4, m_allocUnit);
			m_executor->addTask(new WriteBufferTask(m_file, m_bufferQueue));
		}
	}

	~Imp()
	{
		delete m_file;
		delete[] m_readBuffer;

		if (m_executor) {
			m_executor->cancel();
			delete m_executor;
		}

		delete m_bufferQueue;
	}

	bool put(int frame, UCHAR *data, TUINT32 dataSize);
	UCHAR *get(int pos, TUINT32 *size);

	TThread::Mutex m_mutex;
	TINT64 m_chunkSize;

	ZFile *m_file;
	int m_allocUnit;
	UCHAR *m_readBuffer;
	int m_lx;
	int m_ly;
	int m_bpp;

	bool m_asyncWrite;
	TThread::Executor *m_executor;
	BufferQueue *m_bufferQueue;
};

//------------------------------------------------------------------------------

bool TDiskCachePersist2::Imp::put(int frame, UCHAR *data, TUINT32 dataSize)
{
	if (dataSize != m_chunkSize)
		return false;

	TINT64 pos = frame * m_chunkSize;
	TINT64 sectorCount = pos / m_allocUnit;

	if ((pos % m_allocUnit) != 0)
		++sectorCount;

	pos = sectorCount * m_allocUnit;

	m_file->write(data, dataSize, pos);
	return true;
}

//------------------------------------------------------------------------------

UCHAR *TDiskCachePersist2::Imp::get(int frame, TUINT32 *size)
{
	UCHAR *ret = new UCHAR[TINT32(m_chunkSize)];

	TThread::ScopedLock sl(m_mutex);

	TINT64 pos = frame * m_chunkSize;
	TINT64 sectorCount = pos / m_allocUnit;

	if ((pos % m_allocUnit) != 0)
		++sectorCount;

	pos = sectorCount * m_allocUnit;

	m_file->read(ret, TINT32(m_chunkSize), pos);
	*size = TUINT32(m_chunkSize);
	return ret;
}

//------------------------------------------------------------------------------

TDiskCachePersist2::TDiskCachePersist2(TRasterCodec *codec, const TFilePath &fullpath)
	: TCachePersist(codec), m_imp(new Imp(fullpath, false /*true*/))
{
}

//------------------------------------------------------------------------------

TDiskCachePersist2::~TDiskCachePersist2()
{
	delete m_imp;
}

//------------------------------------------------------------------------------

void TDiskCachePersist2::setFrameSize(int lx, int ly, int bpp)
{
	m_imp->m_lx = lx;
	m_imp->m_ly = ly;
	m_imp->m_bpp = bpp;

	// inizializza m_imp->m_chunkSize in modo che sia un multiplo di m_imp->m_allocUnit
	if (m_codec)
		m_imp->m_chunkSize = m_codec->getMaxCompressionSize(lx * ly * (bpp >> 3));
	else
		m_imp->m_chunkSize = lx * ly * (bpp >> 3);

	TINT64 allocUnitCount = m_imp->m_chunkSize / m_imp->m_allocUnit;
	if ((m_imp->m_chunkSize % m_imp->m_allocUnit) != 0)
		++allocUnitCount;

	m_imp->m_chunkSize = allocUnitCount * m_imp->m_allocUnit;
	delete[] m_imp->m_readBuffer;
	m_imp->m_readBuffer = 0;
}

//------------------------------------------------------------------------------

TRasterP TDiskCachePersist2::doGetRaster(int frame)
{
	TRasterP outRas;
	TUINT32 size;

	if (!m_imp->m_readBuffer)
		m_imp->m_readBuffer = new UCHAR[TINT32(m_imp->m_chunkSize)];

	TINT64 pos = frame * m_imp->m_chunkSize;
	TINT64 sectorCount = pos / m_imp->m_allocUnit;

	if ((pos % m_imp->m_allocUnit) != 0)
		++sectorCount;

	pos = sectorCount * m_imp->m_allocUnit;

	m_imp->m_file->read(m_imp->m_readBuffer, TINT32(m_imp->m_chunkSize), pos);
	size = TUINT32(m_imp->m_chunkSize);

	if (m_codec)
		m_codec->decompress(m_imp->m_readBuffer, size, outRas);
	else {
		switch (m_imp->m_bpp) {
		case 32: {
			TRaster32P ras(m_imp->m_lx, m_imp->m_ly);
			outRas = ras;
		} break;
		case 64: {
			TRaster64P ras(m_imp->m_lx, m_imp->m_ly);
			outRas = ras;
		} break;
		default:
			throw TException("unsupported pixel format");
			break;
		}

		unsigned int rasSize = outRas->getRowSize() * outRas->getLy();
		assert(size >= rasSize);
		outRas->lock();
		memcpy(outRas->getRawData(), m_imp->m_readBuffer, rasSize);
		outRas->unlock();
	}
	return outRas;
}

//------------------------------------------------------------------------------

bool TDiskCachePersist2::doGetRaster(int frame, TRaster32P &ras) const
{
	if (!m_imp->m_readBuffer)
		m_imp->m_readBuffer = new UCHAR[TINT32(m_imp->m_chunkSize)];

	TINT64 pos = frame * m_imp->m_chunkSize;
	TINT64 sectorCount = pos / m_imp->m_allocUnit;

	if ((pos % m_imp->m_allocUnit) != 0)
		++sectorCount;

	pos = sectorCount * m_imp->m_allocUnit;

	TRasterP rasP = ras;
	if (m_codec) {
		m_imp->m_file->read(m_imp->m_readBuffer, TINT32(m_imp->m_chunkSize), pos);
		m_codec->decompress(m_imp->m_readBuffer, TINT32(m_imp->m_chunkSize), rasP);
		assert(rasP->getSize() == ras->getSize());
		ras->copy(rasP);
	} else {
		assert(ras->getLx() == ras->getWrap());
		int rasSize = ras->getRowSize() * ras->getLy();
		ras->lock();
		if (rasSize == m_imp->m_chunkSize)
			m_imp->m_file->read(ras->getRawData(), TINT32(m_imp->m_chunkSize), pos);
		else {
			assert(rasSize < m_imp->m_chunkSize);
			m_imp->m_file->read(m_imp->m_readBuffer, TINT32(m_imp->m_chunkSize), pos);
			memcpy(ras->getRawData(), m_imp->m_readBuffer, rasSize);
		}
		ras->unlock();
	}
	return true;
}

//------------------------------------------------------------------------------

bool TDiskCachePersist2::doPutRaster(int frame, const TRasterP &inRas)
{
	UCHAR *outData = 0;
	TINT32 outDataSize = 0;
	int actualDataSize = 0;

	bool deleteDataBuffer = false;
	if (m_codec) {
		m_codec->compress(inRas, m_imp->m_allocUnit, &outData, outDataSize);
		deleteDataBuffer = true;
		;
		actualDataSize = outDataSize;
	} else {
		assert(inRas->getLx() == inRas->getWrap());
		int rasSize = inRas->getLx() * inRas->getLy() * inRas->getPixelSize();

		outDataSize = TINT32(m_imp->m_chunkSize);
		inRas->lock();
		if (rasSize == m_imp->m_chunkSize)
			outData = inRas->getRawData();
		else {
			if (!m_imp->m_readBuffer)
				m_imp->m_readBuffer = new UCHAR[TINT32(m_imp->m_chunkSize)];

			memcpy(m_imp->m_readBuffer, inRas->getRawData(), rasSize);
			outData = m_imp->m_readBuffer;
		}
		inRas->unlock();
		actualDataSize = rasSize;
	}

	assert((outDataSize % m_imp->m_allocUnit) == 0);

	bool cached = true;
	if (m_imp->m_asyncWrite)
		m_imp->m_bufferQueue->put(frame, outData, actualDataSize, outDataSize);
	else
		cached = m_imp->put(frame, outData, outDataSize);

	if (deleteDataBuffer)
		delete[] outData;
	return cached;
}

//------------------------------------------------------------------------------

UCHAR *TDiskCachePersist2::getRawData(int frame, TINT32 &size, int &lx, int &ly)
{
	TUINT32 inDataSize;
	UCHAR *src = m_imp->get(frame, &inDataSize);
	return m_codec->removeHeader(src, inDataSize, size, lx, ly);
}

//------------------------------------------------------------------------------

void TDiskCachePersist2::onInvalidate()
{
}

//------------------------------------------------------------------------------

void TDiskCachePersist2::onInvalidate(int startFrame, int endFrame)
{
}

//------------------------------------------------------------------------------

TUINT64 TDiskCachePersist2::getUsedSpace()
{
	TFileStatus fs(m_imp->m_file->getFilePath());
	return fs.getSize();
}

//------------------------------------------------------------------------------
#endif //WIN32