scummvm/common/memstream.h
Vladimir Serbinenko 81040b5d20 COMMON: Make memstream support SharedPtr-backed array
This allows to safely implement archive contents caches.

The change to lilliput is needed to allow to keep move-constructot
semantics of ScriptStream which would otherwise disappear because
DisposablePtr is non-copyable
2022-12-01 01:52:46 +01:00

495 lines
12 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_MEMSTREAM_H
#define COMMON_MEMSTREAM_H
#include "common/stream.h"
#include "common/types.h"
#include "common/util.h"
namespace Common {
/**
* @defgroup common_memory_pool Memory stream
* @ingroup common_memory
*
* @brief API for managing the memory stream.
* @{
*/
/**
* Simple memory based 'stream', which implements the ReadStream interface for
* a plain memory block.
*/
class MemoryReadStream : virtual public SeekableReadStream {
private:
struct CastFreeDeleter {
inline void operator()(const byte *object) {
free(const_cast<byte *>(object));
}
};
// Note when using SharedPtr, then deleting is handled
// by SharedPtr and not by CastFreeDeleter
Common::DisposablePtr<const byte, CastFreeDeleter> _ptrOrig;
const byte *_ptr;
uint32 _size;
uint32 _pos;
bool _eos;
public:
MemoryReadStream(MemoryReadStream &&other) : _ptrOrig(Common::move(other._ptrOrig)), _ptr(other._ptr), _size(other._size), _pos(other._pos), _eos(other._eos) {
// other must remaining in a valid state. Let's make it into zero-sized stream.
other._ptr = nullptr;
other._size = 0;
other._pos = 0;
other._eos = false;
}
/**
* This constructor takes a pointer to a memory buffer and a length, and
* wraps it. If disposeMemory is true, the MemoryReadStream takes ownership
* of the buffer and hence free's it when destructed.
*/
MemoryReadStream(const byte *dataPtr, uint32 dataSize, DisposeAfterUse::Flag disposeMemory = DisposeAfterUse::NO) :
_ptrOrig(dataPtr, disposeMemory),
_ptr(dataPtr),
_size(dataSize),
_pos(0),
_eos(false) {}
MemoryReadStream(SharedPtr<byte> dataPtr, uint32 dataSize) :
_ptrOrig(dataPtr),
_ptr(dataPtr.get()),
_size(dataSize),
_pos(0),
_eos(false) {}
uint32 read(void *dataPtr, uint32 dataSize);
bool eos() const { return _eos; }
void clearErr() { _eos = false; }
int64 pos() const { return _pos; }
int64 size() const { return _size; }
bool seek(int64 offs, int whence = SEEK_SET);
};
/**
* This is a MemoryReadStream subclass which adds non-endian
* read methods whose endianness is set on the stream creation.
*/
class MemoryReadStreamEndian : public MemoryReadStream, public SeekableReadStreamEndian {
public:
MemoryReadStreamEndian(const byte *buf, uint32 len, bool bigEndian, DisposeAfterUse::Flag disposeMemory = DisposeAfterUse::NO)
: MemoryReadStream(buf, len, disposeMemory), SeekableReadStreamEndian(bigEndian), ReadStreamEndian(bigEndian) {}
int64 pos() const override { return MemoryReadStream::pos(); }
int64 size() const override { return MemoryReadStream::size(); }
bool seek(int64 offs, int whence = SEEK_SET) override { return MemoryReadStream::seek(offs, whence); }
bool skip(uint32 offset) override { return MemoryReadStream::seek(offset, SEEK_CUR); }
};
/**
* Simple memory based 'stream', which implements the WriteStream interface for
* a plain memory block.
*/
class MemoryWriteStream : public SeekableWriteStream {
private:
const uint32 _bufSize;
protected:
byte *_ptr;
uint32 _pos;
bool _err;
public:
MemoryWriteStream(byte *buf, uint32 len) : _ptr(buf), _bufSize(len), _pos(0), _err(false) {}
uint32 write(const void *dataPtr, uint32 dataSize) override {
// Write at most as many bytes as are still available...
if (dataSize > _bufSize - _pos) {
dataSize = _bufSize - _pos;
// We couldn't write all the data => set error indicator
_err = true;
}
memcpy(_ptr, dataPtr, dataSize);
_ptr += dataSize;
_pos += dataSize;
return dataSize;
}
int64 pos() const override { return _pos; }
int64 size() const override { return _bufSize; }
bool err() const override { return _err; }
void clearErr() override { _err = false; }
bool seek(int64 offset, int whence = SEEK_SET) override { return false; }
};
/**
* MemoryWriteStream subclass with ability to set stream position indicator.
*/
class SeekableMemoryWriteStream : public MemoryWriteStream {
private:
byte *_ptrOrig;
public:
SeekableMemoryWriteStream(byte *buf, uint32 len) : MemoryWriteStream(buf, len), _ptrOrig(buf) {}
bool seek(int64 offset, int whence = SEEK_SET) override {
switch (whence) {
case SEEK_END:
// SEEK_END works just like SEEK_SET, only 'reversed',
// i.e. from the end.
offset = size() + offset;
// Fall through
case SEEK_SET:
// Fall through
default:
_ptr = _ptrOrig + offset;
_pos = offset;
break;
case SEEK_CUR:
_ptr += offset;
_pos += offset;
break;
}
// Post-Condition
if ((int32)_pos > size()) {
_pos = size();
_ptr = _ptrOrig + _pos;
}
return true;
}
};
/**
* A sort of hybrid between MemoryWriteStream and Array classes. A stream
* that grows as it's written to.
*/
class MemoryWriteStreamDynamic : public SeekableWriteStream {
protected:
uint32 _capacity;
uint32 _size;
byte *_ptr;
byte *_data;
uint32 _pos;
DisposeAfterUse::Flag _disposeMemory;
void ensureCapacity(uint32 capacity) {
if (capacity <= _capacity)
return;
byte *old_data = _data;
_capacity = capacity;
_data = (byte *)malloc(_capacity);
_ptr = _data + _pos;
if (old_data) {
// Copy old data
memcpy(_data, old_data, _size);
free(old_data);
}
}
/** Round up capacity to the next power of 2.
* A minimal capacity of 8 is used.
*/
static size_t roundUpCapacity(size_t capacity) {
size_t capa = 8;
while (capa < capacity)
capa <<= 1;
return capa;
}
public:
explicit MemoryWriteStreamDynamic(DisposeAfterUse::Flag disposeMemory) : _capacity(0), _size(0), _ptr(nullptr), _data(nullptr), _pos(0), _disposeMemory(disposeMemory) {}
~MemoryWriteStreamDynamic() {
if (_disposeMemory)
free(_data);
}
uint32 write(const void *dataPtr, uint32 dataSize) override {
if ((_pos + dataSize) >= _capacity)
ensureCapacity(roundUpCapacity(_pos + dataSize));
memcpy(_ptr, dataPtr, dataSize);
_ptr += dataSize;
_pos += dataSize;
if (_pos > _size)
_size = _pos;
return dataSize;
}
int64 pos() const override { return _pos; }
int64 size() const override { return _size; }
byte *getData() { return _data; }
bool seek(int64 offs, int whence = SEEK_SET) override {
// Pre-Condition
assert(_pos <= _size);
switch (whence) {
case SEEK_END:
// SEEK_END works just like SEEK_SET, only 'reversed', i.e. from the end.
offs = _size + offs;
// Fall through
case SEEK_SET:
// Fall through
default:
_ptr = _data + offs;
_pos = offs;
break;
case SEEK_CUR:
_ptr += offs;
_pos += offs;
break;
}
assert(_pos <= _size);
return true;
}
};
/**
* MemoryStream based on RingBuffer. Grows if has insufficient buffer size.
*/
class MemoryReadWriteStream : public SeekableReadStream, public SeekableWriteStream {
private:
uint32 _capacity;
uint32 _size;
byte *_data;
uint32 _writePos, _readPos, _pos, _length;
DisposeAfterUse::Flag _disposeMemory;
bool _eos;
void ensureCapacity(uint32 new_len) {
if (new_len <= _capacity)
return;
byte *old_data = _data;
uint32 oldCapacity = _capacity;
_capacity = MAX(new_len + 32, _capacity * 2);
_data = (byte *)malloc(_capacity);
if (old_data) {
// Copy old data
if (_readPos < _writePos) {
memcpy(_data, old_data + _readPos, _writePos - _readPos);
_writePos = _length;
_readPos = 0;
} else {
memcpy(_data, old_data + _readPos, oldCapacity - _readPos);
memcpy(_data + oldCapacity - _readPos, old_data, _writePos);
_writePos = _length;
_readPos = 0;
}
free(old_data);
}
}
public:
explicit MemoryReadWriteStream(DisposeAfterUse::Flag disposeMemory) : _capacity(0), _size(0), _data(nullptr), _writePos(0), _readPos(0), _pos(0), _length(0), _disposeMemory(disposeMemory), _eos(false) {}
~MemoryReadWriteStream() {
if (_disposeMemory)
free(_data);
}
uint32 write(const void *dataPtr, uint32 dataSize) override {
ensureCapacity(_length + dataSize);
if (_writePos + dataSize < _capacity) {
memcpy(_data + _writePos, dataPtr, dataSize);
} else {
memcpy(_data + _writePos, dataPtr, _capacity - _writePos);
const byte *shiftedPtr = (const byte *)dataPtr + _capacity - _writePos;
memcpy(_data, shiftedPtr, dataSize - (_capacity - _writePos));
}
_writePos = (_writePos + dataSize) % _capacity;
_pos += dataSize;
_length += dataSize;
if (_pos > _size)
_size = _pos;
return dataSize;
}
uint32 read(void *dataPtr, uint32 dataSize) override {
if (_length < dataSize) {
dataSize = _length;
_eos = true;
}
if (dataSize == 0 || _capacity == 0) return 0;
if (_readPos + dataSize < _capacity) {
memcpy(dataPtr, _data + _readPos, dataSize);
} else {
memcpy(dataPtr, _data + _readPos, _capacity - _readPos);
byte *shiftedPtr = (byte *)dataPtr + _capacity - _readPos;
memcpy(shiftedPtr, _data, dataSize - (_capacity - _readPos));
}
_readPos = (_readPos + dataSize) % _capacity;
_length -= dataSize;
return dataSize;
}
bool seek(int64 offset, int whence) override {
switch (whence) {
case SEEK_END:
// SEEK_END works just like SEEK_SET, only 'reversed',
// i.e. from the end.
offset = size() + offset;
// Fall through
case SEEK_SET:
// Fall through
default:
_writePos = offset;
_readPos = offset;
break;
case SEEK_CUR:
// Not supported
return false;
}
// Post-Condition
_eos = (int64)_readPos >= size();
return true;
}
int64 pos() const override { return _pos - _length; }
int64 size() const override { return _size; }
bool eos() const override { return _eos; }
void clearErr() override { _eos = false; }
byte *getData() { return _data; }
};
/**
* A seekable read and writeable memory stream that operates on an already existing memory buffer
*/
class MemorySeekableReadWriteStream : public SeekableReadStream, public SeekableWriteStream {
private:
const uint32 _bufSize;
byte *_ptrOrig;
byte *_ptr;
uint32 _pos;
bool _err;
bool _eos;
DisposeAfterUse::Flag _disposeMemory;
public:
MemorySeekableReadWriteStream(byte *buf, uint32 len, DisposeAfterUse::Flag disposeMemory = DisposeAfterUse::NO) :
_ptrOrig(buf), _ptr(buf), _bufSize(len), _pos(0), _err(false), _eos(false), _disposeMemory(disposeMemory) {}
~MemorySeekableReadWriteStream() {
if (_disposeMemory) {
free(_ptrOrig);
}
}
uint32 write(const void *dataPtr, uint32 dataSize) override {
// Write at most as many bytes as are still available...
if (dataSize > _bufSize - _pos) {
dataSize = _bufSize - _pos;
// We couldn't write all the data => set error indicator
_err = true;
}
memcpy(_ptr, dataPtr, dataSize);
_ptr += dataSize;
_pos += dataSize;
return dataSize;
}
int64 pos() const override { return _pos; }
int64 size() const override { return _bufSize; }
bool eos() const override { return _eos; }
bool err() const override { return _err; }
void clearErr() override { _err = false; }
inline void rewind(int32 bytes) {
seek(pos() - bytes);
}
byte peekByte() {
if (_bufSize - _pos <= 0) {
_eos = true;
return 0;
}
return *_ptr;
}
uint32 read(void *dataPtr, uint32 dataSize) override {
// Read at most as many bytes as are still available...
if (dataSize > _bufSize - _pos) {
dataSize = _bufSize - _pos;
_eos = true;
}
memcpy(dataPtr, _ptr, dataSize);
_ptr += dataSize;
_pos += dataSize;
return dataSize;
}
bool seek(int64 offset, int whence = SEEK_SET) override {
switch (whence) {
case SEEK_END:
// SEEK_END works just like SEEK_SET, only 'reversed',
// i.e. from the end.
offset = size() + offset;
// Fall through
case SEEK_SET:
// Fall through
default:
_ptr = _ptrOrig + offset;
_pos = offset;
break;
case SEEK_CUR:
_ptr += offset;
_pos += offset;
break;
}
// Post-Condition
if ((int32)_pos > size()) {
_pos = size();
_ptr = _ptrOrig + _pos;
}
_eos = false;
return true;
}
};
/** @} */
} // End of namespace Common
#endif