Save States: Make states a bit more robust to changes and compress their contents (breaks save state compatibility)
This commit is contained in:
parent
c8f6822ed7
commit
afa55938ec
6 changed files with 85 additions and 153 deletions
|
@ -6,47 +6,26 @@
|
|||
|
||||
void RewindData::GetStateData(stringstream &stateData)
|
||||
{
|
||||
unsigned long length = OriginalSaveStateSize;
|
||||
uint8_t* buffer = new uint8_t[length];
|
||||
uncompress(buffer, &length, SaveStateData.data(), (unsigned long)SaveStateData.size());
|
||||
stateData.write((char*)buffer, length);
|
||||
delete[] buffer;
|
||||
stateData.write((char*)SaveStateData.data(), SaveStateData.size());
|
||||
}
|
||||
|
||||
void RewindData::LoadState(shared_ptr<Console> &console)
|
||||
{
|
||||
if(SaveStateData.size() > 0 && OriginalSaveStateSize > 0) {
|
||||
unsigned long length = OriginalSaveStateSize;
|
||||
uint8_t* buffer = new uint8_t[length];
|
||||
uncompress(buffer, &length, SaveStateData.data(), (unsigned long)SaveStateData.size());
|
||||
|
||||
if(SaveStateData.size() > 0) {
|
||||
stringstream stream;
|
||||
stream.write((char*)buffer, length);
|
||||
stream.write((char*)SaveStateData.data(), SaveStateData.size());
|
||||
stream.seekg(0, ios::beg);
|
||||
|
||||
console->Deserialize(stream, SaveStateManager::FileFormatVersion);
|
||||
delete[] buffer;
|
||||
}
|
||||
}
|
||||
|
||||
void RewindData::CompressState(string stateData, vector<uint8_t>& compressedState)
|
||||
{
|
||||
unsigned long compressedSize = compressBound((unsigned long)stateData.size());
|
||||
uint8_t* compressedData = new uint8_t[compressedSize];
|
||||
compress2(compressedData, &compressedSize, (unsigned char*)stateData.c_str(), (unsigned long)stateData.size(), MZ_BEST_SPEED);
|
||||
compressedState = vector<uint8_t>(compressedData, compressedData + compressedSize);
|
||||
delete[] compressedData;
|
||||
}
|
||||
|
||||
void RewindData::SaveState(shared_ptr<Console> &console)
|
||||
{
|
||||
std::stringstream state;
|
||||
console->Serialize(state);
|
||||
|
||||
string stateData = state.str();
|
||||
vector<uint8_t> compressedState;
|
||||
CompressState(stateData, compressedState);
|
||||
SaveStateData = compressedState;
|
||||
OriginalSaveStateSize = (uint32_t)stateData.size();
|
||||
string data = state.str();
|
||||
SaveStateData = vector<uint8_t>(data.c_str(), data.c_str()+data.size());
|
||||
FrameCount = 0;
|
||||
}
|
||||
|
|
|
@ -9,9 +9,6 @@ class RewindData
|
|||
{
|
||||
private:
|
||||
vector<uint8_t> SaveStateData;
|
||||
uint32_t OriginalSaveStateSize = 0;
|
||||
|
||||
void CompressState(string stateData, vector<uint8_t> &compressedState);
|
||||
|
||||
public:
|
||||
std::deque<ControlDeviceState> InputLogs[BaseControlDevice::PortCount];
|
||||
|
|
|
@ -133,7 +133,7 @@ bool SaveStateManager::LoadState(istream &stream, bool hashCheckRequired)
|
|||
}
|
||||
|
||||
stream.read((char*)&fileFormatVersion, sizeof(fileFormatVersion));
|
||||
if(fileFormatVersion <= 4) {
|
||||
if(fileFormatVersion <= 5) {
|
||||
MessageManager::DisplayMessage("SaveStates", "SaveStateIncompatibleVersion");
|
||||
return false;
|
||||
} else {
|
||||
|
|
|
@ -14,7 +14,7 @@ private:
|
|||
string GetStateFilepath(int stateIndex);
|
||||
|
||||
public:
|
||||
static constexpr uint32_t FileFormatVersion = 5;
|
||||
static constexpr uint32_t FileFormatVersion = 6;
|
||||
|
||||
SaveStateManager(shared_ptr<Console> console);
|
||||
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
#include <algorithm>
|
||||
#include "Serializer.h"
|
||||
#include "ISerializable.h"
|
||||
#include "miniz.h"
|
||||
|
||||
Serializer::Serializer(uint32_t version)
|
||||
{
|
||||
_version = version;
|
||||
|
||||
_streamSize = 0x50000;
|
||||
_stream = new uint8_t[_streamSize];
|
||||
_position = 0;
|
||||
_block.Data = vector<uint8_t>(0x50000);
|
||||
_block.Position = 0;
|
||||
_saving = true;
|
||||
}
|
||||
|
||||
|
@ -17,56 +17,36 @@ Serializer::Serializer(istream &file, uint32_t version)
|
|||
{
|
||||
_version = version;
|
||||
|
||||
_position = 0;
|
||||
_block.Position = 0;
|
||||
_saving = false;
|
||||
|
||||
file.read((char*)&_streamSize, sizeof(_streamSize));
|
||||
_stream = new uint8_t[_streamSize];
|
||||
file.read((char*)_stream, _streamSize);
|
||||
}
|
||||
uint32_t decompressedSize;
|
||||
file.read((char*)&decompressedSize, sizeof(decompressedSize));
|
||||
|
||||
Serializer::~Serializer()
|
||||
{
|
||||
delete[] _stream;
|
||||
uint32_t compressedSize;
|
||||
file.read((char*)&compressedSize, sizeof(compressedSize));
|
||||
|
||||
vector<uint8_t> compressedData(compressedSize, 0);
|
||||
file.read((char*)compressedData.data(), compressedSize);
|
||||
|
||||
_block.Data = vector<uint8_t>(decompressedSize, 0);
|
||||
|
||||
unsigned long decompSize;
|
||||
uncompress(_block.Data.data(), &decompSize, compressedData.data(), (unsigned long)compressedData.size());
|
||||
}
|
||||
|
||||
void Serializer::EnsureCapacity(uint32_t typeSize)
|
||||
{
|
||||
//Make sure the current block/stream is large enough to fit the next write
|
||||
uint32_t oldSize;
|
||||
uint32_t sizeRequired;
|
||||
uint8_t *oldBuffer;
|
||||
if(_inBlock) {
|
||||
oldBuffer = _blockBuffer;
|
||||
oldSize = _blockSize;
|
||||
sizeRequired = _blockPosition + typeSize;
|
||||
} else {
|
||||
oldBuffer = _stream;
|
||||
oldSize = _streamSize;
|
||||
sizeRequired = _position + typeSize;
|
||||
uint32_t oldSize = (uint32_t)_block.Data.size();
|
||||
uint32_t sizeRequired = _block.Position + typeSize;
|
||||
|
||||
uint32_t newSize = oldSize;
|
||||
while(newSize < sizeRequired) {
|
||||
newSize *= 2;
|
||||
}
|
||||
|
||||
uint8_t *newBuffer = nullptr;
|
||||
uint32_t newSize = oldSize * 2;
|
||||
if(oldSize < sizeRequired) {
|
||||
while(newSize < sizeRequired) {
|
||||
newSize *= 2;
|
||||
}
|
||||
|
||||
newBuffer = new uint8_t[newSize];
|
||||
memcpy(newBuffer, oldBuffer, oldSize);
|
||||
delete[] oldBuffer;
|
||||
}
|
||||
|
||||
if(newBuffer) {
|
||||
if(_inBlock) {
|
||||
_blockBuffer = newBuffer;
|
||||
_blockSize = newSize;
|
||||
} else {
|
||||
_stream = newBuffer;
|
||||
_streamSize = newSize;
|
||||
}
|
||||
}
|
||||
_block.Data.resize(newSize);
|
||||
}
|
||||
|
||||
void Serializer::RecursiveStream()
|
||||
|
@ -75,41 +55,47 @@ void Serializer::RecursiveStream()
|
|||
|
||||
void Serializer::StreamStartBlock()
|
||||
{
|
||||
if(_inBlock) {
|
||||
throw std::runtime_error("Cannot start a new block before ending the last block");
|
||||
}
|
||||
BlockData block;
|
||||
block.Position = 0;
|
||||
|
||||
if(!_saving) {
|
||||
InternalStream(_blockSize);
|
||||
_blockSize = std::min(_blockSize, (uint32_t)0xFFFFF);
|
||||
_blockBuffer = new uint8_t[_blockSize];
|
||||
ArrayInfo<uint8_t> arrayInfo = { _blockBuffer, _blockSize };
|
||||
InternalStream(arrayInfo);
|
||||
VectorInfo<uint8_t> vectorInfo = { &block.Data };
|
||||
InternalStream(vectorInfo);
|
||||
} else {
|
||||
_blockSize = 0x100;
|
||||
_blockBuffer = new uint8_t[_blockSize];
|
||||
block.Data = vector<uint8_t>(0x100);
|
||||
}
|
||||
_blockPosition = 0;
|
||||
_inBlock = true;
|
||||
|
||||
_blocks.push_back(_block);
|
||||
_block = block;
|
||||
}
|
||||
|
||||
void Serializer::StreamEndBlock()
|
||||
{
|
||||
_inBlock = false;
|
||||
if(_saving) {
|
||||
InternalStream(_blockPosition);
|
||||
ArrayInfo<uint8_t> arrayInfo = { _blockBuffer, _blockPosition };
|
||||
InternalStream(arrayInfo);
|
||||
if(_blocks.empty()) {
|
||||
throw std::runtime_error("Invalid call to end block");
|
||||
}
|
||||
|
||||
delete[] _blockBuffer;
|
||||
_blockBuffer = nullptr;
|
||||
BlockData block = _block;
|
||||
|
||||
_block = _blocks.back();
|
||||
_blocks.pop_back();
|
||||
|
||||
if(_saving) {
|
||||
ArrayInfo<uint8_t> arrayInfo { block.Data.data(), block.Position };
|
||||
InternalStream(arrayInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void Serializer::Save(ostream& file)
|
||||
void Serializer::Save(ostream& file, int compressionLevel)
|
||||
{
|
||||
file.write((char*)&_position, sizeof(_position));
|
||||
file.write((char*)_stream, _position);
|
||||
unsigned long compressedSize = compressBound((unsigned long)_block.Position);
|
||||
uint8_t* compressedData = new uint8_t[compressedSize];
|
||||
compress2(compressedData, &compressedSize, (unsigned char*)_block.Data.data(), (unsigned long)_block.Position, compressionLevel);
|
||||
|
||||
file.write((char*)&_block.Position, sizeof(_block.Position));
|
||||
file.write((char*)&compressedSize, sizeof(compressedSize));
|
||||
file.write((char*)compressedData, compressedSize);
|
||||
delete[] compressedData;
|
||||
}
|
||||
|
||||
void Serializer::WriteEmptyBlock(ostream* file)
|
||||
|
@ -127,14 +113,13 @@ void Serializer::SkipBlock(istream* file)
|
|||
|
||||
void Serializer::Stream(ISerializable &obj)
|
||||
{
|
||||
//StreamStartBlock();
|
||||
StreamStartBlock();
|
||||
obj.Serialize(*this);
|
||||
//StreamEndBlock();
|
||||
StreamEndBlock();
|
||||
}
|
||||
|
||||
void Serializer::Stream(ISerializable *obj)
|
||||
{
|
||||
//StreamStartBlock();
|
||||
StreamStartBlock();
|
||||
obj->Serialize(*this);
|
||||
//StreamEndBlock();
|
||||
StreamEndBlock();
|
||||
}
|
|
@ -25,32 +25,27 @@ struct ValueInfo
|
|||
T DefaultValue;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct EmptyInfo
|
||||
struct BlockData
|
||||
{
|
||||
T Empty;
|
||||
vector<uint8_t> Data;
|
||||
uint32_t Position;
|
||||
};
|
||||
|
||||
class Serializer
|
||||
{
|
||||
private:
|
||||
uint8_t* _stream = nullptr;
|
||||
uint32_t _position = 0;
|
||||
uint32_t _streamSize = 0;
|
||||
vector<BlockData> _blocks;
|
||||
|
||||
BlockData _block;
|
||||
|
||||
uint32_t _version = 0;
|
||||
|
||||
bool _inBlock = false;
|
||||
uint8_t* _blockBuffer = nullptr;
|
||||
uint32_t _blockSize = 0;
|
||||
uint32_t _blockPosition = 0;
|
||||
|
||||
bool _saving = false;
|
||||
|
||||
private:
|
||||
void EnsureCapacity(uint32_t typeSize);
|
||||
|
||||
template<typename T> void StreamElement(T &value, T defaultValue = T());
|
||||
template<typename T> void InternalStream(EmptyInfo<T> &info);
|
||||
|
||||
template<typename T> void InternalStream(ArrayInfo<T> &info);
|
||||
template<typename T> void InternalStream(VectorInfo<T> &info);
|
||||
template<typename T> void InternalStream(ValueInfo<T> &info);
|
||||
|
@ -65,7 +60,6 @@ private:
|
|||
public:
|
||||
Serializer(uint32_t version);
|
||||
Serializer(istream &file, uint32_t version);
|
||||
~Serializer();
|
||||
|
||||
uint32_t GetVersion() { return _version; }
|
||||
bool IsSaving() { return _saving; }
|
||||
|
@ -74,7 +68,7 @@ public:
|
|||
template<typename T> void StreamArray(T *array, uint32_t size);
|
||||
template<typename T> void StreamVector(vector<T> &list);
|
||||
|
||||
void Save(ostream &file);
|
||||
void Save(ostream &file, int compressionLevel = 1);
|
||||
|
||||
void Stream(ISerializable &obj);
|
||||
void Stream(ISerializable *obj);
|
||||
|
@ -91,63 +85,40 @@ void Serializer::StreamElement(T &value, T defaultValue)
|
|||
int typeSize = sizeof(T);
|
||||
|
||||
EnsureCapacity(typeSize);
|
||||
|
||||
for(int i = 0; i < typeSize; i++) {
|
||||
if(_inBlock) {
|
||||
_blockBuffer[_blockPosition++] = bytes[i];
|
||||
} else {
|
||||
_stream[_position++] = bytes[i];
|
||||
}
|
||||
_block.Data[_block.Position++] = bytes[i];
|
||||
}
|
||||
} else {
|
||||
if(_inBlock) {
|
||||
if(_blockPosition + sizeof(T) <= _blockSize) {
|
||||
memcpy(&value, _blockBuffer + _blockPosition, sizeof(T));
|
||||
_blockPosition += sizeof(T);
|
||||
} else {
|
||||
value = defaultValue;
|
||||
_blockPosition = _blockSize;
|
||||
}
|
||||
if(_block.Position + sizeof(T) <= _block.Data.size()) {
|
||||
memcpy(&value, _block.Data.data() + _block.Position, sizeof(T));
|
||||
_block.Position += sizeof(T);
|
||||
} else {
|
||||
if(_position + sizeof(T) <= _streamSize) {
|
||||
memcpy(&value, _stream + _position, sizeof(T));
|
||||
_position += sizeof(T);
|
||||
} else {
|
||||
value = defaultValue;
|
||||
_position = _streamSize;
|
||||
}
|
||||
value = defaultValue;
|
||||
_block.Position = (uint32_t)_block.Data.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Serializer::InternalStream(EmptyInfo<T> &info)
|
||||
{
|
||||
if(_inBlock) {
|
||||
_blockPosition += sizeof(T);
|
||||
} else {
|
||||
_position += sizeof(T);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Serializer::InternalStream(ArrayInfo<T> &info)
|
||||
{
|
||||
T* pointer = info.Array;
|
||||
|
||||
uint32_t count = info.ElementCount;
|
||||
StreamElement<uint32_t>(count);
|
||||
|
||||
if(!_saving) {
|
||||
//Reset array to 0 before loading from file
|
||||
memset(info.Array, 0, sizeof(T) * info.ElementCount);
|
||||
memset(info.Array, 0, info.ElementCount * sizeof(T));
|
||||
}
|
||||
|
||||
//Load the number of elements requested, or the maximum possible (based on what is present in the save state)
|
||||
for(uint32_t i = 0; i < info.ElementCount && i < count; i++) {
|
||||
StreamElement<T>(*pointer);
|
||||
pointer++;
|
||||
EnsureCapacity(info.ElementCount * sizeof(T));
|
||||
|
||||
if(_saving) {
|
||||
memcpy(_block.Data.data() + _block.Position, info.Array, info.ElementCount * sizeof(T));
|
||||
} else {
|
||||
memcpy(info.Array, _block.Data.data() + _block.Position, info.ElementCount * sizeof(T));
|
||||
}
|
||||
_block.Position += info.ElementCount * sizeof(T);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
|
Loading…
Add table
Reference in a new issue