You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw-tes3mp/components/files/constrainedfiledatastream.cpp

183 lines
4.6 KiB
C++

#include "constrainedfiledatastream.hpp"
#include "lowlevelfile.hpp"
#include <stdexcept>
#include <cassert>
#include <stdint.h>
namespace {
class ConstrainedDataStream : public Ogre::DataStream {
public:
static const size_t sBufferSize = 4096; // somewhat arbitrary though 64KB buffers didn't seem to improve performance any
static const size_t sBufferThreshold = 1024; // reads larger than this bypass buffering as cost of memcpy outweighs cost of system call
ConstrainedDataStream(const Ogre::String &fname, size_t start, size_t length)
: Ogre::DataStream(fname)
{
mFile.open (fname.c_str ());
mSize = length != 0xFFFFFFFF ? length : mFile.size () - start;
mPos = 0;
mOrigin = start;
mExtent = start + mSize;
mBufferOrigin = 0;
mBufferExtent = 0;
}
size_t read(void* buf, size_t count)
{
try
{
assert (mPos <= mSize);
uint8_t * out = reinterpret_cast <uint8_t *> (buf);
size_t posBeg = mOrigin + mPos;
size_t posEnd = posBeg + count;
if (posEnd > mExtent)
posEnd = mExtent;
size_t posCur = posBeg;
while (posCur != posEnd)
{
size_t readLeft = posEnd - posCur;
if (posCur < mBufferOrigin || posCur >= mBufferExtent)
{
if (readLeft >= sBufferThreshold || (posCur == mOrigin && posEnd == mExtent))
{
assert (mFile.tell () == mBufferExtent);
if (posCur != mBufferExtent)
mFile.seek (posCur);
posCur += mFile.read (out, readLeft);
mBufferOrigin = mBufferExtent = posCur;
mPos = posCur - mOrigin;
return posCur - posBeg;
}
else
{
size_t newBufferOrigin;
if ((posCur < mBufferOrigin) && (mBufferOrigin - posCur < sBufferSize))
newBufferOrigin = std::max (mOrigin, mBufferOrigin > sBufferSize ? mBufferOrigin - sBufferSize : 0);
else
newBufferOrigin = posCur;
fill (newBufferOrigin);
}
}
size_t xfer = std::min (readLeft, mBufferExtent - posCur);
memcpy (out, mBuffer + (posCur - mBufferOrigin), xfer);
posCur += xfer;
out += xfer;
}
count = posEnd - posBeg;
mPos += count;
return count;
}
catch (std::exception& e)
{
std::stringstream error;
error << "Failed to read '" << mName << "': " << e.what();
throw std::runtime_error(error.str());
}
}
void skip(long count)
{
assert (mPos <= mSize);
if((count >= 0 && (size_t)count <= mSize-mPos) ||
(count < 0 && (size_t)-count <= mPos))
mPos += count;
}
void seek(size_t pos)
{
assert (mPos <= mSize);
if (pos < mSize)
mPos = pos;
}
virtual size_t tell() const
{
assert (mPos <= mSize);
return mPos;
}
virtual bool eof() const
{
assert (mPos <= mSize);
return mPos == mSize;
}
virtual void close()
{
mFile.close();
}
private:
void fill (size_t newOrigin)
{
assert (mFile.tell () == mBufferExtent);
size_t newExtent = newOrigin + sBufferSize;
if (newExtent > mExtent)
newExtent = mExtent;
size_t oldExtent = mBufferExtent;
if (newOrigin != oldExtent)
mFile.seek (newOrigin);
mBufferOrigin = mBufferExtent = newOrigin;
size_t amountRequested = newExtent - newOrigin;
size_t amountRead = mFile.read (mBuffer, amountRequested);
if (amountRead != amountRequested)
throw std::runtime_error ("An unexpected condition occurred while reading from a file.");
mBufferExtent = newExtent;
}
LowLevelFile mFile;
size_t mOrigin;
size_t mExtent;
size_t mPos;
uint8_t mBuffer [sBufferSize];
size_t mBufferOrigin;
size_t mBufferExtent;
};
} // end of unnamed namespace
Ogre::DataStreamPtr openConstrainedFileDataStream (char const * filename, size_t offset, size_t length)
{
return Ogre::DataStreamPtr(new ConstrainedDataStream(filename, offset, length));
}