Clean up GameSave somewhat

Namely:

 - get rid of unsafe memory management;
   - use vectors / Planes everywhere;
   - return a vector from serialization functions;
   - have read functions take a vector;
 - improve constness;
 - hide a few implementation details from GameSave.h;
 - get rid of GameSave copy constructor;
 - better member initialization;
 - use the slightly more C++-looking BZ2 wrappers.

The BSON library still takes ownership of the data it parses, and GameSave
ownership is still a joke. Those will need to be fixed later.
This commit is contained in:
Tamás Bálint Misius 2022-11-10 14:59:09 +01:00
parent f70cc705cb
commit ab600780d0
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
10 changed files with 242 additions and 401 deletions

View File

@ -153,5 +153,6 @@
#define GLASS_DISP 0.07
#define SDEUT
#define R_TEMP 22
#endif /* CONFIG_H */

View File

@ -36,7 +36,14 @@ BZ2WCompressResult BZ2WCompress(std::vector<char> &dest, const char *srcData, si
{
return BZ2WCompressLimit;
}
dest.resize(newSize);
try
{
dest.resize(newSize);
}
catch (const std::bad_alloc &)
{
return BZ2WCompressNomem;
}
stream.next_out = &dest[stream.total_out_lo32];
stream.avail_out = dest.size() - stream.total_out_lo32;
if (BZ2_bzCompress(&stream, BZ_FINISH) == BZ_STREAM_END)
@ -75,7 +82,14 @@ BZ2WDecompressResult BZ2WDecompress(std::vector<char> &dest, const char *srcData
{
return BZ2WDecompressLimit;
}
dest.resize(newSize);
try
{
dest.resize(newSize);
}
catch (const std::bad_alloc &)
{
return BZ2WDecompressNomem;
}
stream.next_out = &dest[stream.total_out_lo32];
stream.avail_out = dest.size() - stream.total_out_lo32;
switch (BZ2_bzDecompress(&stream))

View File

@ -2,6 +2,7 @@
#include "client/http/Request.h" // includes curl.h, needs to come first to silence a warning on windows
#include <cstring>
#include <cstdlib>
#include <vector>
#include <map>
@ -496,8 +497,6 @@ User Client::GetAuthUser()
RequestStatus Client::UploadSave(SaveInfo & save)
{
lastError = "";
unsigned int gameDataLength;
char * gameData = NULL;
int dataStatus;
ByteString data;
ByteString userID = ByteString::Build(authUser.UserID);
@ -511,15 +510,15 @@ RequestStatus Client::UploadSave(SaveInfo & save)
save.SetID(0);
gameData = save.GetGameSave()->Serialise(gameDataLength);
auto [ fromNewerVersion, gameData ] = save.GetGameSave()->Serialise();
if (!gameData)
if (!gameData.size())
{
lastError = "Cannot serialize game save";
return RequestFailure;
}
#if defined(SNAPSHOT) || defined(BETA) || defined(DEBUG) || MOD_ID > 0
else if (save.gameSave->fromNewerVersion && save.GetPublished())
else if (fromNewerVersion && save.GetPublished())
{
lastError = "Cannot publish save, incompatible with latest release version.";
return RequestFailure;
@ -529,7 +528,7 @@ RequestStatus Client::UploadSave(SaveInfo & save)
data = http::Request::SimpleAuth(SCHEME SERVER "/Save.api", &dataStatus, userID, authUser.SessionID, {
{ "Name", save.GetName().ToUtf8() },
{ "Description", save.GetDescription().ToUtf8() },
{ "Data:save.bin", ByteString(gameData, gameData + gameDataLength) },
{ "Data:save.bin", ByteString(gameData.begin(), gameData.end()) },
{ "Publish", save.GetPublished() ? "Public" : "Private" },
});
}
@ -551,7 +550,6 @@ RequestStatus Client::UploadSave(SaveInfo & save)
else
save.SetID(saveID);
}
delete[] gameData;
return ret;
}
@ -623,17 +621,11 @@ ByteString Client::AddStamp(GameSave * saveData)
}
saveData->authors = stampInfo;
unsigned int gameDataLength;
char * gameData = saveData->Serialise(gameDataLength);
if (gameData == NULL)
auto [ _, gameData ] = saveData->Serialise();
if (!gameData.size())
return "";
std::ofstream stampStream;
stampStream.open(filename.c_str(), std::ios::binary);
stampStream.write((const char *)gameData, gameDataLength);
stampStream.close();
delete[] gameData;
Platform::WriteFile(gameData, filename);
stampIDs.push_front(saveID);

View File

@ -7,7 +7,7 @@
#include <set>
#include <cmath>
#include <bzlib.h>
#include "bzip2/bz2wrap.h"
#include "Config.h"
#include "Format.h"
#include "hmap.h"
@ -17,49 +17,17 @@
#include "common/tpt-minmax.h"
#include "common/tpt-compat.h"
#include "bson/BSON.h"
GameSave::GameSave(const GameSave & save):
majorVersion(save.majorVersion),
waterEEnabled(save.waterEEnabled),
legacyEnable(save.legacyEnable),
gravityEnable(save.gravityEnable),
aheatEnable(save.aheatEnable),
paused(save.paused),
gravityMode(save.gravityMode),
customGravityX(save.customGravityX),
customGravityY(save.customGravityY),
airMode(save.airMode),
ambientAirTemp(save.ambientAirTemp),
edgeMode(save.edgeMode),
signs(save.signs),
stkm(save.stkm),
palette(save.palette),
pmapbits(save.pmapbits)
{
InitData();
hasPressure = save.hasPressure;
hasAmbientHeat = save.hasAmbientHeat;
setSize(save.blockWidth, save.blockHeight);
std::copy(save.particles, save.particles+NPART, particles);
for (int j = 0; j < blockHeight; j++)
{
std::copy(save.blockMap[j], save.blockMap[j]+blockWidth, blockMap[j]);
std::copy(save.fanVelX[j], save.fanVelX[j]+blockWidth, fanVelX[j]);
std::copy(save.fanVelY[j], save.fanVelY[j]+blockWidth, fanVelY[j]);
std::copy(save.pressure[j], save.pressure[j]+blockWidth, pressure[j]);
std::copy(save.velocityX[j], save.velocityX[j]+blockWidth, velocityX[j]);
std::copy(save.velocityY[j], save.velocityY[j]+blockWidth, velocityY[j]);
std::copy(save.ambientHeat[j], save.ambientHeat[j]+blockWidth, ambientHeat[j]);
}
particlesCount = save.particlesCount;
authors = save.authors;
}
static void ConvertJsonToBson(bson *b, Json::Value j, int depth = 0);
static void ConvertBsonToJson(bson_iterator *b, Json::Value *j, int depth = 0);
static void CheckBsonFieldUser(bson_iterator iter, const char *field, unsigned char **data, unsigned int *fieldLen);
static void CheckBsonFieldBool(bson_iterator iter, const char *field, bool *flag);
static void CheckBsonFieldInt(bson_iterator iter, const char *field, int *setting);
static void CheckBsonFieldFloat(bson_iterator iter, const char *field, float *setting);
GameSave::GameSave(int width, int height)
{
InitData();
InitVars();
setSize(width, height);
}
@ -68,8 +36,6 @@ GameSave::GameSave(const std::vector<char> &data)
blockWidth = 0;
blockHeight = 0;
InitData();
InitVars();
try
{
Expand(data);
@ -77,67 +43,25 @@ GameSave::GameSave(const std::vector<char> &data)
catch(ParseException & e)
{
std::cout << e.what() << std::endl;
dealloc(); //Free any allocated memory
throw;
}
}
// Called on every new GameSave, including the copy constructor
void GameSave::InitData()
{
blockMap = NULL;
fanVelX = NULL;
fanVelY = NULL;
particles = NULL;
pressure = NULL;
velocityX = NULL;
velocityY = NULL;
ambientHeat = NULL;
fromNewerVersion = false;
hasPressure = false;
hasAmbientHeat = false;
authors.clear();
}
// Called on every new GameSave, except the copy constructor
void GameSave::InitVars()
{
majorVersion = 0;
minorVersion = 0;
waterEEnabled = false;
legacyEnable = false;
gravityEnable = false;
aheatEnable = false;
paused = false;
gravityMode = 0;
customGravityX = 0.0f;
customGravityY = 0.0f;
airMode = 0;
ambientAirTemp = R_TEMP + 273.15f;
edgeMode = 0;
translated.x = translated.y = 0;
pmapbits = 8; // default to 8 bits for older saves
}
void GameSave::Expand(const std::vector<char> &data)
{
InitVars();
read(&data[0], data.size());
}
void GameSave::read(const char * data, int dataSize)
{
if(dataSize > 15)
try
{
if(data.size() > 15)
{
if ((data[0]==0x66 && data[1]==0x75 && data[2]==0x43) || (data[0]==0x50 && data[1]==0x53 && data[2]==0x76))
{
readPSv(data, dataSize);
readPSv(data);
}
else if(data[0] == 'O' && data[1] == 'P' && data[2] == 'S')
{
if (data[3] != '1')
throw ParseException(ParseException::WrongVersion, "Save format from newer version");
readOPS(data, dataSize);
readOPS(data);
}
else
{
@ -149,59 +73,45 @@ void GameSave::read(const char * data, int dataSize)
{
throw ParseException(ParseException::Corrupt, "No data");
}
}
template <typename T>
T ** GameSave::Allocate2DArray(int blockWidth, int blockHeight, T defaultVal)
{
T ** temp = new T*[blockHeight];
for (int y = 0; y < blockHeight; y++)
{
temp[y] = new T[blockWidth];
std::fill(&temp[y][0], &temp[y][0]+blockWidth, defaultVal);
}
return temp;
catch (const std::bad_alloc &)
{
throw ParseException(ParseException::Corrupt, "Cannot allocate memory");
}
}
void GameSave::setSize(int newWidth, int newHeight)
{
this->blockWidth = newWidth;
this->blockHeight = newHeight;
blockWidth = newWidth;
blockHeight = newHeight;
particlesCount = 0;
particles = new Particle[NPART];
particles = std::vector<Particle>(NPART);
blockMap = Allocate2DArray<unsigned char>(blockWidth, blockHeight, 0);
fanVelX = Allocate2DArray<float>(blockWidth, blockHeight, 0.0f);
fanVelY = Allocate2DArray<float>(blockWidth, blockHeight, 0.0f);
pressure = Allocate2DArray<float>(blockWidth, blockHeight, 0.0f);
velocityX = Allocate2DArray<float>(blockWidth, blockHeight, 0.0f);
velocityY = Allocate2DArray<float>(blockWidth, blockHeight, 0.0f);
ambientHeat = Allocate2DArray<float>(blockWidth, blockHeight, 0.0f);
blockMap = Plane<unsigned char>(blockWidth, blockHeight, 0);
fanVelX = Plane<float>(blockWidth, blockHeight, 0.0f);
fanVelY = Plane<float>(blockWidth, blockHeight, 0.0f);
pressure = Plane<float>(blockWidth, blockHeight, 0.0f);
velocityX = Plane<float>(blockWidth, blockHeight, 0.0f);
velocityY = Plane<float>(blockWidth, blockHeight, 0.0f);
ambientHeat = Plane<float>(blockWidth, blockHeight, 0.0f);
}
std::vector<char> GameSave::Serialise()
{
unsigned int dataSize;
char * data = Serialise(dataSize);
if (data == NULL)
return std::vector<char>();
std::vector<char> dataVect(data, data+dataSize);
delete[] data;
return dataVect;
}
char * GameSave::Serialise(unsigned int & dataSize)
std::pair<bool, std::vector<char>> GameSave::Serialise() const
{
try
{
return serialiseOPS(dataSize);
return serialiseOPS();
}
catch (const std::bad_alloc &)
{
std::cout << "Save error, out of memory" << std::endl;
}
catch (BuildException & e)
{
std::cout << e.what() << std::endl;
return NULL;
}
return { false, {} };
}
vector2d GameSave::Translate(vector2d translate)
@ -312,16 +222,13 @@ void GameSave::Transform(matrix2d transform, vector2d translate, vector2d transl
int x, y, nx, ny, newBlockWidth = newWidth / CELL, newBlockHeight = newHeight / CELL;
vector2d pos, vel;
unsigned char ** blockMapNew;
float **fanVelXNew, **fanVelYNew, **pressureNew, **velocityXNew, **velocityYNew, **ambientHeatNew;
blockMapNew = Allocate2DArray<unsigned char>(newBlockWidth, newBlockHeight, 0);
fanVelXNew = Allocate2DArray<float>(newBlockWidth, newBlockHeight, 0.0f);
fanVelYNew = Allocate2DArray<float>(newBlockWidth, newBlockHeight, 0.0f);
pressureNew = Allocate2DArray<float>(newBlockWidth, newBlockHeight, 0.0f);
velocityXNew = Allocate2DArray<float>(newBlockWidth, newBlockHeight, 0.0f);
velocityYNew = Allocate2DArray<float>(newBlockWidth, newBlockHeight, 0.0f);
ambientHeatNew = Allocate2DArray<float>(newBlockWidth, newBlockHeight, 0.0f);
Plane<unsigned char> blockMapNew(newBlockWidth, newBlockHeight, 0);
Plane<float> fanVelXNew(newBlockWidth, newBlockHeight, 0.0f);
Plane<float> fanVelYNew(newBlockWidth, newBlockHeight, 0.0f);
Plane<float> pressureNew(newBlockWidth, newBlockHeight, 0.0f);
Plane<float> velocityXNew(newBlockWidth, newBlockHeight, 0.0f);
Plane<float> velocityYNew(newBlockWidth, newBlockHeight, 0.0f);
Plane<float> ambientHeatNew(newBlockWidth, newBlockHeight, 0.0f);
// Match these up with the matrices provided in GameView::OnKeyPress.
bool patchPipeR = transform.a == 0 && transform.b == 1 && transform.c == -1 && transform.d == 0;
@ -423,28 +330,9 @@ void GameSave::Transform(matrix2d transform, vector2d translate, vector2d transl
}
translated = v2d_add(m2d_multiply_v2d(transform, translated), translateReal);
for (int j = 0; j < blockHeight; j++)
{
delete[] blockMap[j];
delete[] fanVelX[j];
delete[] fanVelY[j];
delete[] pressure[j];
delete[] velocityX[j];
delete[] velocityY[j];
delete[] ambientHeat[j];
}
blockWidth = newBlockWidth;
blockHeight = newBlockHeight;
delete[] blockMap;
delete[] fanVelX;
delete[] fanVelY;
delete[] pressure;
delete[] velocityX;
delete[] velocityY;
delete[] ambientHeat;
blockMap = blockMapNew;
fanVelX = fanVelXNew;
fanVelY = fanVelYNew;
@ -454,7 +342,7 @@ void GameSave::Transform(matrix2d transform, vector2d translate, vector2d transl
ambientHeat = ambientHeatNew;
}
void GameSave::CheckBsonFieldUser(bson_iterator iter, const char *field, unsigned char **data, unsigned int *fieldLen)
static void CheckBsonFieldUser(bson_iterator iter, const char *field, unsigned char **data, unsigned int *fieldLen)
{
if (!strcmp(bson_iterator_key(&iter), field))
{
@ -469,7 +357,7 @@ void GameSave::CheckBsonFieldUser(bson_iterator iter, const char *field, unsigne
}
}
void GameSave::CheckBsonFieldBool(bson_iterator iter, const char *field, bool *flag)
static void CheckBsonFieldBool(bson_iterator iter, const char *field, bool *flag)
{
if (!strcmp(bson_iterator_key(&iter), field))
{
@ -484,7 +372,7 @@ void GameSave::CheckBsonFieldBool(bson_iterator iter, const char *field, bool *f
}
}
void GameSave::CheckBsonFieldInt(bson_iterator iter, const char *field, int *setting)
static void CheckBsonFieldInt(bson_iterator iter, const char *field, int *setting)
{
if (!strcmp(bson_iterator_key(&iter), field))
{
@ -499,7 +387,7 @@ void GameSave::CheckBsonFieldInt(bson_iterator iter, const char *field, int *set
}
}
void GameSave::CheckBsonFieldFloat(bson_iterator iter, const char *field, float *setting)
static void CheckBsonFieldFloat(bson_iterator iter, const char *field, float *setting)
{
if (!strcmp(bson_iterator_key(&iter), field))
{
@ -514,11 +402,12 @@ void GameSave::CheckBsonFieldFloat(bson_iterator iter, const char *field, float
}
}
void GameSave::readOPS(const char * data, int dataLength)
void GameSave::readOPS(const std::vector<char> &data)
{
unsigned char *inputData = (unsigned char*)data, *bsonData = NULL, *partsData = NULL, *partsPosData = NULL, *fanData = NULL, *wallData = NULL, *soapLinkData = NULL;
unsigned char *inputData = (unsigned char*)&data[0], *partsData = NULL, *partsPosData = NULL, *fanData = NULL, *wallData = NULL, *soapLinkData = NULL;
unsigned char *pressData = NULL, *vxData = NULL, *vyData = NULL, *ambientData = NULL;
unsigned int inputDataLen = dataLength, bsonDataLen = 0, partsDataLen, partsPosDataLen, fanDataLen, wallDataLen, soapLinkDataLen;
unsigned int inputDataLen = data.size(), bsonDataLen = 0, partsDataLen, partsPosDataLen, fanDataLen, wallDataLen, soapLinkDataLen;
unsigned int pressDataLen, vxDataLen, vyDataLen, ambientDataLen;
unsigned partsCount = 0;
unsigned int blockX, blockY, blockW, blockH, fullX, fullY, fullW, fullH;
@ -572,26 +461,32 @@ void GameSave::readOPS(const char * data, int dataLength)
bsonDataLen |= ((unsigned)inputData[11]) << 24;
//Check for overflows, don't load saves larger than 200MB
unsigned int toAlloc = bsonDataLen+1;
unsigned int toAlloc = bsonDataLen;
if (toAlloc > 209715200 || !toAlloc)
throw ParseException(ParseException::InvalidDimensions, "Save data too large, refusing");
bsonData = (unsigned char*)malloc(toAlloc);
if (!bsonData)
throw ParseException(ParseException::InternalError, "Unable to allocate memory");
//Make sure bsonData is null terminated, since all string functions need null terminated strings
//(bson_iterator_key returns a pointer into bsonData, which is then used with strcmp)
bsonData[bsonDataLen] = 0;
int bz2ret;
if ((bz2ret = BZ2_bzBuffToBuffDecompress((char*)bsonData, &bsonDataLen, (char*)(inputData+12), inputDataLen-12, 0, 0)) != BZ_OK)
{
throw ParseException(ParseException::Corrupt, String::Build("Unable to decompress (ret ", bz2ret, ")"));
std::vector<char> bsonData;
switch (auto status = BZ2WDecompress(bsonData, (char *)(inputData + 12), inputDataLen - 12, toAlloc))
{
case BZ2WDecompressOk: break;
case BZ2WDecompressNomem: throw ParseException(ParseException::Corrupt, "Cannot allocate memory");
default: throw ParseException(ParseException::Corrupt, String::Build("Cannot decompress: status ", int(status)));
}
bsonDataLen = bsonData.size();
//Make sure bsonData is null terminated, since all string functions need null terminated strings
//(bson_iterator_key returns a pointer into bsonData, which is then used with strcmp)
bsonData.push_back(0);
// apparently bson_* takes ownership of the data passed into it?????????
auto *pleaseFixMe = (char *)malloc(bsonData.size());
std::copy(bsonData.begin(), bsonData.end(), pleaseFixMe);
bson_init_data_size(&b, pleaseFixMe, bsonDataLen);
}
set_bson_err_handler([](const char* err) { throw ParseException(ParseException::Corrupt, "BSON error when parsing save: " + ByteString(err).FromUtf8()); });
bson_init_data_size(&b, (char*)bsonData, bsonDataLen);
bson_iterator_init(&iter, &b);
std::vector<sign> tempSigns;
@ -1346,9 +1241,10 @@ void GameSave::readOPS(const char * data, int dataLength)
}
}
void GameSave::readPSv(const char * saveDataChar, int dataLength)
void GameSave::readPSv(const std::vector<char> &dataVec)
{
unsigned char * saveData = (unsigned char *)saveDataChar;
unsigned char * saveData = (unsigned char *)&dataVec[0];
auto dataLength = int(dataVec.size());
int q,j,k,x,y,p=0, ver, pty, ty, legacy_beta=0;
int bx0=0, by0=0, bw, bh, w, h, y0 = 0, x0 = 0;
int new_format = 0, ttv = 0;
@ -1421,17 +1317,16 @@ void GameSave::readPSv(const char * saveDataChar, int dataLength)
if (size > 209715200 || !size)
throw ParseException(ParseException::InvalidDimensions, "Save data too large");
auto dataPtr = std::unique_ptr<unsigned char[]>(new unsigned char[size]);
unsigned char *data = dataPtr.get();
if (!data)
throw ParseException(ParseException::Corrupt, "Cannot allocate memory");
std::vector<char> data;
switch (auto status = BZ2WDecompress(data, (char *)(saveData + 12), dataLength - 12, size))
{
case BZ2WDecompressOk: break;
case BZ2WDecompressNomem: throw ParseException(ParseException::Corrupt, "Cannot allocate memory");
default: throw ParseException(ParseException::Corrupt, String::Build("Cannot decompress: status ", int(status)));
}
setSize(bw, bh);
int bzStatus = 0;
if ((bzStatus = BZ2_bzBuffToBuffDecompress((char *)data, (unsigned *)&size, (char *)(saveData+12), dataLength-12, 0, 0)))
throw ParseException(ParseException::Corrupt, String::Build("Cannot decompress: ", bzStatus));
dataLength = size;
dataLength = data.size();
#ifdef DEBUG
std::cout << "Parsing " << dataLength << " bytes of data, version " << ver << std::endl;
@ -1451,11 +1346,7 @@ void GameSave::readPSv(const char * saveDataChar, int dataLength)
airMode = 0;
}
auto particleIDMapPtr = std::unique_ptr<int[]>(new int[XRES*YRES]);
int *particleIDMap = particleIDMapPtr.get();
std::fill(&particleIDMap[0], &particleIDMap[XRES*YRES], 0);
if (!particleIDMap)
throw ParseException(ParseException::Corrupt, "Cannot allocate memory");
std::vector<int> particleIDMap(XRES * YRES, 0);
// load the required air state
for (y=by0; y<by0+bh; y++)
@ -1575,7 +1466,7 @@ void GameSave::readPSv(const char * saveDataChar, int dataLength)
}
if (j)
{
memset(particles+k, 0, sizeof(Particle));
memset(&particles[0]+k, 0, sizeof(Particle));
particles[k].type = j;
if (j == PT_COAL)
particles[k].tmp = 50;
@ -1980,7 +1871,7 @@ void GameSave::readPSv(const char * saveDataChar, int dataLength)
throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
if(x>254)
x = 254;
memcpy(tempSignText, data+p, x);
memcpy(tempSignText, &data[0]+p, x);
tempSignText[x] = 0;
tempSign.text = format::CleanString(ByteString(tempSignText).FromUtf8(), true, true, true).Substr(0, 45);
if (tempSign.text == "{t}")
@ -2009,7 +1900,7 @@ void GameSave::readPSv(const char * saveDataChar, int dataLength)
minimumMinorVersion = minor;\
}
char * GameSave::serialiseOPS(unsigned int & dataLength)
std::pair<bool, std::vector<char>> GameSave::serialiseOPS() const
{
int blockX, blockY, blockW, blockH, fullX, fullY, fullW, fullH;
int x, y, i;
@ -2033,16 +1924,13 @@ char * GameSave::serialiseOPS(unsigned int & dataLength)
fullH = blockH*CELL;
// Copy fan and wall data
auto wallData = std::unique_ptr<unsigned char[]>(new unsigned char[blockWidth*blockHeight]);
std::vector<unsigned char> wallData(blockWidth*blockHeight);
bool hasWallData = false;
auto fanData = std::unique_ptr<unsigned char[]>(new unsigned char[blockWidth*blockHeight*2]);
auto pressData = std::unique_ptr<unsigned char[]>(new unsigned char[blockWidth*blockHeight*2]);
auto vxData = std::unique_ptr<unsigned char[]>(new unsigned char[blockWidth*blockHeight*2]);
auto vyData = std::unique_ptr<unsigned char[]>(new unsigned char[blockWidth*blockHeight*2]);
auto ambientData = std::unique_ptr<unsigned char[]>(new unsigned char[blockWidth*blockHeight*2]);
std::fill(&ambientData[0], &ambientData[blockWidth*blockHeight*2], 0);
if (!wallData || !fanData || !pressData || !vxData || !vyData || !ambientData)
throw BuildException("Save error, out of memory (blockmaps)");
std::vector<unsigned char> fanData(blockWidth*blockHeight*2);
std::vector<unsigned char> pressData(blockWidth*blockHeight*2);
std::vector<unsigned char> vxData(blockWidth*blockHeight*2);
std::vector<unsigned char> vyData(blockWidth*blockHeight*2);
std::vector<unsigned char> ambientData(blockWidth*blockHeight*2, 0);
unsigned int wallDataLen = blockWidth*blockHeight, fanDataLen = 0, pressDataLen = 0, vxDataLen = 0, vyDataLen = 0, ambientDataLen = 0;
for (x = blockX; x < blockX+blockW; x++)
@ -2099,16 +1987,10 @@ char * GameSave::serialiseOPS(unsigned int & dataLength)
//partsPosLastMap is pmap for the last particle in each position
//partsPosCount is the number of particles in each position
//partsPosLink contains, for each particle, (i<<8)|1 of the next particle in the same position
auto partsPosFirstMap = std::unique_ptr<unsigned[]>(new unsigned[fullW*fullH]);
auto partsPosLastMap = std::unique_ptr<unsigned[]>(new unsigned[fullW*fullH]);
auto partsPosCount = std::unique_ptr<unsigned[]>(new unsigned[fullW*fullH]);
auto partsPosLink = std::unique_ptr<unsigned[]>(new unsigned[NPART]);
if (!partsPosFirstMap || !partsPosLastMap || !partsPosCount || !partsPosLink)
throw BuildException("Save error, out of memory (partmaps)");
std::fill(&partsPosFirstMap[0], &partsPosFirstMap[fullW*fullH], 0);
std::fill(&partsPosLastMap[0], &partsPosLastMap[fullW*fullH], 0);
std::fill(&partsPosCount[0], &partsPosCount[fullW*fullH], 0);
std::fill(&partsPosLink[0], &partsPosLink[NPART], 0);
std::vector<unsigned> partsPosFirstMap(fullW*fullH, 0);
std::vector<unsigned> partsPosLastMap(fullW*fullH, 0);
std::vector<unsigned> partsPosCount(fullW*fullH, 0);
std::vector<unsigned> partsPosLink(NPART, 0);
unsigned int soapCount = 0;
for(i = 0; i < particlesCount; i++)
{
@ -2136,10 +2018,8 @@ char * GameSave::serialiseOPS(unsigned int & dataLength)
}
//Store number of particles in each position
auto partsPosData = std::unique_ptr<unsigned char[]>(new unsigned char[fullW*fullH*3]);
std::vector<unsigned char> partsPosData(fullW*fullH*3);
unsigned int partsPosDataLen = 0;
if (!partsPosData)
throw BuildException("Save error, out of memory (partposdata)");
for (y=0;y<fullH;y++)
{
for (x=0;x<fullW;x++)
@ -2166,12 +2046,10 @@ char * GameSave::serialiseOPS(unsigned int & dataLength)
// Allocate enough space to store all Particles and 3 bytes on top of that per Particle, for the field descriptors.
// In practice, a Particle will never need as much space in the save as in memory; this is just an upper bound to simplify allocation.
auto partsData = std::unique_ptr<unsigned char[]>(new unsigned char[NPART * (sizeof(Particle)+3)]);
std::vector<unsigned char> partsData(NPART * (sizeof(Particle)+3));
unsigned int partsDataLen = 0;
auto partsSaveIndex = std::unique_ptr<unsigned[]>(new unsigned[NPART]);
std::vector<unsigned> partsSaveIndex(NPART);
unsigned int partsCount = 0;
if (!partsData || !partsSaveIndex)
throw BuildException("Save error, out of memory (partsdata)");
std::fill(&partsSaveIndex[0], &partsSaveIndex[NPART], 0);
for (y=0;y<fullH;y++)
{
@ -2443,15 +2321,10 @@ char * GameSave::serialiseOPS(unsigned int & dataLength)
}
}
unsigned char *soapLinkData = NULL;
auto soapLinkDataPtr = std::unique_ptr<unsigned char[]>();
unsigned int soapLinkDataLen = 0;
std::vector<unsigned char> soapLinkData(3*soapCount);
if (soapCount)
{
soapLinkData = new unsigned char[3*soapCount];
if (!soapLinkData)
throw BuildException("Save error, out of memory (SOAP)");
soapLinkDataPtr = std::unique_ptr<unsigned char[]>(soapLinkData);
//Iterate through particles in the same order that they were saved
for (y=0;y<fullH;y++)
@ -2503,10 +2376,11 @@ char * GameSave::serialiseOPS(unsigned int & dataLength)
}
}
bool fakeFromNewerVersion = false;
#if defined(SNAPSHOT) || defined(BETA) || defined(DEBUG) || MOD_ID > 0
// Mark save as incompatible with latest release
if (minimumMajorVersion > SAVE_VERSION || (minimumMajorVersion == SAVE_VERSION && minimumMinorVersion > MINOR_VERSION))
fromNewerVersion = true;
fakeFromNewerVersion = true;
#endif
bson b;
@ -2583,37 +2457,37 @@ char * GameSave::serialiseOPS(unsigned int & dataLength)
}
bson_append_int(&b, "pmapbits", pmapbits);
if (partsData && partsDataLen)
if (partsDataLen)
{
bson_append_binary(&b, "parts", (char)BSON_BIN_USER, (const char *)partsData.get(), partsDataLen);
bson_append_binary(&b, "parts", (char)BSON_BIN_USER, (const char *)&partsData[0], partsDataLen);
if (palette.size())
{
bson_append_start_array(&b, "palette");
for(std::vector<PaletteItem>::iterator iter = palette.begin(), end = palette.end(); iter != end; ++iter)
for(auto iter = palette.begin(), end = palette.end(); iter != end; ++iter)
{
bson_append_int(&b, (*iter).first.c_str(), (*iter).second);
}
bson_append_finish_array(&b);
}
if (partsPosData && partsPosDataLen)
bson_append_binary(&b, "partsPos", (char)BSON_BIN_USER, (const char *)partsPosData.get(), partsPosDataLen);
if (partsPosDataLen)
bson_append_binary(&b, "partsPos", (char)BSON_BIN_USER, (const char *)&partsPosData[0], partsPosDataLen);
}
if (wallData && hasWallData)
bson_append_binary(&b, "wallMap", (char)BSON_BIN_USER, (const char *)wallData.get(), wallDataLen);
if (fanData && fanDataLen)
bson_append_binary(&b, "fanMap", (char)BSON_BIN_USER, (const char *)fanData.get(), fanDataLen);
if (pressData && hasPressure && pressDataLen)
bson_append_binary(&b, "pressMap", (char)BSON_BIN_USER, (const char*)pressData.get(), pressDataLen);
if (vxData && hasPressure && vxDataLen)
bson_append_binary(&b, "vxMap", (char)BSON_BIN_USER, (const char*)vxData.get(), vxDataLen);
if (vyData && hasPressure && vyDataLen)
bson_append_binary(&b, "vyMap", (char)BSON_BIN_USER, (const char*)vyData.get(), vyDataLen);
if (ambientData && hasAmbientHeat && this->aheatEnable && ambientDataLen)
bson_append_binary(&b, "ambientMap", (char)BSON_BIN_USER, (const char*)ambientData.get(), ambientDataLen);
if (soapLinkData && soapLinkDataLen)
bson_append_binary(&b, "soapLinks", (char)BSON_BIN_USER, (const char *)soapLinkData, soapLinkDataLen);
if (hasWallData)
bson_append_binary(&b, "wallMap", (char)BSON_BIN_USER, (const char *)&wallData[0], wallDataLen);
if (fanDataLen)
bson_append_binary(&b, "fanMap", (char)BSON_BIN_USER, (const char *)&fanData[0], fanDataLen);
if (hasPressure && pressDataLen)
bson_append_binary(&b, "pressMap", (char)BSON_BIN_USER, (const char*)&pressData[0], pressDataLen);
if (hasPressure && vxDataLen)
bson_append_binary(&b, "vxMap", (char)BSON_BIN_USER, (const char*)&vxData[0], vxDataLen);
if (hasPressure && vyDataLen)
bson_append_binary(&b, "vyMap", (char)BSON_BIN_USER, (const char*)&vyData[0], vyDataLen);
if (hasAmbientHeat && this->aheatEnable && ambientDataLen)
bson_append_binary(&b, "ambientMap", (char)BSON_BIN_USER, (const char*)&ambientData[0], ambientDataLen);
if (soapLinkDataLen)
bson_append_binary(&b, "soapLinks", (char)BSON_BIN_USER, (const char *)&soapLinkData[0], soapLinkDataLen);
unsigned int signsCount = 0;
for (size_t i = 0; i < signs.size(); i++)
{
@ -2650,40 +2524,43 @@ char * GameSave::serialiseOPS(unsigned int & dataLength)
unsigned char *finalData = (unsigned char*)bson_data(&b);
unsigned int finalDataLen = bson_size(&b);
auto outputData = std::unique_ptr<unsigned char[]>(new unsigned char[finalDataLen*2+12]);
if (!outputData)
throw BuildException(String::Build("Save error, out of memory (finalData): ", finalDataLen*2+12));
outputData[0] = 'O';
outputData[1] = 'P';
outputData[2] = 'S';
outputData[3] = '1';
outputData[4] = SAVE_VERSION;
outputData[5] = CELL;
outputData[6] = blockW;
outputData[7] = blockH;
outputData[8] = finalDataLen;
outputData[9] = finalDataLen >> 8;
outputData[10] = finalDataLen >> 16;
outputData[11] = finalDataLen >> 24;
unsigned int compressedSize = finalDataLen*2, bz2ret;
if ((bz2ret = BZ2_bzBuffToBuffCompress((char*)(outputData.get()+12), &compressedSize, (char*)finalData, bson_size(&b), 9, 0, 0)) != BZ_OK)
std::vector<char> outputData;
switch (auto status = BZ2WCompress(outputData, (char *)finalData, finalDataLen))
{
throw BuildException(String::Build("Save error, could not compress (ret ", bz2ret, ")"));
case BZ2WCompressOk: break;
case BZ2WCompressNomem: throw BuildException(String::Build("Save error, out of memory"));
default: throw BuildException(String::Build("Cannot compress: status ", int(status)));
}
auto compressedSize = int(outputData.size());
#ifdef DEBUG
printf("compressed data: %d\n", compressedSize);
#endif
dataLength = compressedSize + 12;
outputData.resize(compressedSize + 12);
char *saveData = new char[dataLength];
std::copy(&outputData[0], &outputData[dataLength], &saveData[0]);
return saveData;
auto header = (unsigned char *)&outputData[compressedSize];
header[0] = 'O';
header[1] = 'P';
header[2] = 'S';
header[3] = '1';
header[4] = SAVE_VERSION;
header[5] = CELL;
header[6] = blockW;
header[7] = blockH;
header[8] = finalDataLen;
header[9] = finalDataLen >> 8;
header[10] = finalDataLen >> 16;
header[11] = finalDataLen >> 24;
// move header to front
std::rotate(outputData.begin(), outputData.begin() + compressedSize, outputData.end());
return { fakeFromNewerVersion, outputData };
}
void GameSave::ConvertBsonToJson(bson_iterator *iter, Json::Value *j, int depth)
static void ConvertBsonToJson(bson_iterator *iter, Json::Value *j, int depth)
{
bson_iterator subiter;
bson_iterator_subiterator(iter, &subiter);
@ -2754,7 +2631,7 @@ std::set<int> GetNestedSaveIDs(Json::Value j)
}
// converts a json object to bson
void GameSave::ConvertJsonToBson(bson *b, Json::Value j, int depth)
static void ConvertJsonToBson(bson *b, Json::Value j, int depth)
{
Json::Value::Members members = j.getMemberNames();
for (Json::Value::Members::iterator iter = members.begin(), end = members.end(); iter != end; ++iter)
@ -2805,19 +2682,6 @@ void GameSave::ConvertJsonToBson(bson *b, Json::Value j, int depth)
}
}
// deallocates a pointer to a 2D array and sets it to NULL
template <typename T>
void GameSave::Deallocate2DArray(T ***array, int blockHeight)
{
if (*array)
{
for (int y = 0; y < blockHeight; y++)
delete[] (*array)[y];
delete[] (*array);
*array = NULL;
}
}
bool GameSave::TypeInCtype(int type, int ctype)
{
return ctype >= 0 && ctype < PT_NUM &&
@ -2843,27 +2707,6 @@ bool GameSave::PressureInTmp3(int type)
return type == PT_QRTZ || type == PT_GLAS || type == PT_TUNG;
}
void GameSave::dealloc()
{
if (particles)
{
delete[] particles;
particles = NULL;
}
Deallocate2DArray<unsigned char>(&blockMap, blockHeight);
Deallocate2DArray<float>(&fanVelX, blockHeight);
Deallocate2DArray<float>(&fanVelY, blockHeight);
Deallocate2DArray<float>(&pressure, blockHeight);
Deallocate2DArray<float>(&velocityX, blockHeight);
Deallocate2DArray<float>(&velocityY, blockHeight);
Deallocate2DArray<float>(&ambientHeat, blockHeight);
}
GameSave::~GameSave()
{
dealloc();
}
GameSave& GameSave::operator << (Particle &v)
{
if(particlesCount<NPART && v.type)

View File

@ -1,12 +1,12 @@
#ifndef The_Powder_Toy_GameSave_h
#define The_Powder_Toy_GameSave_h
#pragma once
#include "Config.h"
#include <vector>
#include "common/String.h"
#include "simulation/Sign.h"
#include "simulation/Particle.h"
#include "Misc.h"
#include "bson/BSON.h"
#include <json/json.h>
struct sign;
@ -43,63 +43,80 @@ public:
bool rocketBoots2 = false;
bool fan1 = false;
bool fan2 = false;
std::vector<unsigned int> rocketBootsFigh = std::vector<unsigned int>();
std::vector<unsigned int> fanFigh = std::vector<unsigned int>();
std::vector<unsigned int> rocketBootsFigh;
std::vector<unsigned int> fanFigh;
StkmData() = default;
StkmData(const StkmData & stkmData):
rocketBoots1(stkmData.rocketBoots1),
rocketBoots2(stkmData.rocketBoots2),
fan1(stkmData.fan1),
fan2(stkmData.fan2),
rocketBootsFigh(stkmData.rocketBootsFigh),
fanFigh(stkmData.fanFigh)
{
}
bool hasData()
bool hasData() const
{
return rocketBoots1 || rocketBoots2 || fan1 || fan2
|| rocketBootsFigh.size() || fanFigh.size();
}
};
template<class Item>
struct Plane
{
int width = 0;
int height = 0;
std::vector<Item> items;
// invariant: items.size() == width * height
Item *operator [](int y)
{
return &items[y * width];
}
const Item *operator [](int y) const
{
return &items[y * width];
}
Plane() = default;
Plane(int newWidth, int newHeight, Item defaultVal) : width(newWidth), height(newHeight), items(width * height, defaultVal)
{
}
};
class GameSave
{
public:
// number of pixels translated. When translating CELL pixels, shift all CELL grids
vector2d translated = { 0, 0 };
void readOPS(const std::vector<char> &data);
void readPSv(const std::vector<char> &data);
std::pair<bool, std::vector<char>> serialiseOPS() const;
int blockWidth, blockHeight;
bool fromNewerVersion;
int majorVersion, minorVersion;
bool hasPressure;
bool hasAmbientHeat;
public:
int blockWidth = 0;
int blockHeight = 0;
bool fromNewerVersion = false;
int majorVersion = 0;
int minorVersion = 0;
bool hasPressure = false;
bool hasAmbientHeat = false;
//Simulation data
//int ** particleMap;
int particlesCount;
Particle * particles;
unsigned char ** blockMap;
float ** fanVelX;
float ** fanVelY;
float ** pressure;
float ** velocityX;
float ** velocityY;
float ** ambientHeat;
int particlesCount = 0;
std::vector<Particle> particles;
Plane<unsigned char> blockMap;
Plane<float> fanVelX;
Plane<float> fanVelY;
Plane<float> pressure;
Plane<float> velocityX;
Plane<float> velocityY;
Plane<float> ambientHeat;
//Simulation Options
bool waterEEnabled;
bool legacyEnable;
bool gravityEnable;
bool aheatEnable;
bool paused;
int gravityMode;
float customGravityX;
float customGravityY;
int airMode;
float ambientAirTemp;
int edgeMode;
bool waterEEnabled = false;
bool legacyEnable = false;
bool gravityEnable = false;
bool aheatEnable = false;
bool paused = false;
int gravityMode = 0;
float customGravityX = 0.0f;
float customGravityY = 0.0f;
int airMode = 0;
float ambientAirTemp = R_TEMP + 273.15f;
int edgeMode = 0;
//Signs
std::vector<sign> signs;
@ -112,16 +129,13 @@ public:
// author information
Json::Value authors;
int pmapbits;
int pmapbits = 8; // default to 8 bits for older saves
GameSave();
GameSave(const GameSave & save);
GameSave(int width, int height);
GameSave(const std::vector<char> &data);
~GameSave();
void setSize(int width, int height);
char * Serialise(unsigned int & dataSize);
std::vector<char> Serialise();
// return value is [ fakeFromNewerVersion, gameData ]
std::pair<bool, std::vector<char>> Serialise() const;
vector2d Translate(vector2d translate);
void Transform(matrix2d transform, vector2d translate);
void Transform(matrix2d transform, vector2d translate, vector2d translateReal, int newWidth, int newHeight);
@ -135,26 +149,4 @@ public:
GameSave& operator << (Particle &v);
GameSave& operator << (sign &v);
private:
// number of pixels translated. When translating CELL pixels, shift all CELL grids
vector2d translated;
void InitData();
void InitVars();
void CheckBsonFieldUser(bson_iterator iter, const char *field, unsigned char **data, unsigned int *fieldLen);
void CheckBsonFieldBool(bson_iterator iter, const char *field, bool *flag);
void CheckBsonFieldInt(bson_iterator iter, const char *field, int *setting);
void CheckBsonFieldFloat(bson_iterator iter, const char *field, float *setting);
template <typename T> T ** Allocate2DArray(int blockWidth, int blockHeight, T defaultVal);
template <typename T> void Deallocate2DArray(T ***array, int blockHeight);
void dealloc();
void read(const char * data, int dataSize);
void readOPS(const char * data, int dataLength);
void readPSv(const char * data, int dataLength);
char * serialiseOPS(unsigned int & dataSize);
void ConvertJsonToBson(bson *b, Json::Value j, int depth = 0);
void ConvertBsonToJson(bson_iterator *b, Json::Value *j, int depth = 0);
};
#endif

View File

@ -1203,7 +1203,7 @@ void GameController::OpenLocalSaveWindow(bool asCurrent)
gameModel->SetSaveFile(&tempSave, gameView->ShiftBehaviour());
Platform::MakeDirectory(LOCAL_SAVE_DIR);
std::vector<char> saveData = gameSave->Serialise();
auto [ _, saveData ] = gameSave->Serialise();
if (saveData.size() == 0)
new ErrorMessage("Error", "Unable to serialize game data.");
else if (!Platform::WriteFile(saveData, gameModel->GetSaveFile()->GetName()))

View File

@ -112,7 +112,7 @@ void LocalSaveActivity::saveWrite(ByteString finalFilename)
localSaveInfo["date"] = (Json::Value::UInt64)time(NULL);
Client::Ref().SaveAuthorInfo(&localSaveInfo);
gameSave->authors = localSaveInfo;
std::vector<char> saveData = gameSave->Serialise();
auto [ _, saveData ] = gameSave->Serialise();
if (saveData.size() == 0)
new ErrorMessage("Error", "Unable to serialize game data.");
else if (!Platform::WriteFile(saveData, finalFilename))

View File

@ -4,7 +4,6 @@
#include "Config.h"
//#include "Simulation.h"
#define R_TEMP 22
#define MAX_TEMP 9999
#define MIN_TEMP 0
#define O_MAX_TEMP 3500

View File

@ -11,7 +11,7 @@ sign::sign(String text_, int x_, int y_, Justification justification_):
{
}
String sign::getDisplayText(Simulation *sim, int &x0, int &y0, int &w, int &h, bool colorize, bool *v95)
String sign::getDisplayText(Simulation *sim, int &x0, int &y0, int &w, int &h, bool colorize, bool *v95) const
{
String drawable_text;
auto si = std::make_pair(0, Type::Normal);
@ -142,7 +142,7 @@ String sign::getDisplayText(Simulation *sim, int &x0, int &y0, int &w, int &h, b
return drawable_text;
}
std::pair<int, sign::Type> sign::split()
std::pair<int, sign::Type> sign::split() const
{
String::size_type pipe = 0;
if (text.size() >= 4 && text.front() == '{' && text.back() == '}')

View File

@ -32,8 +32,8 @@ struct sign
String text;
sign(String text_, int x_, int y_, Justification justification_);
String getDisplayText(Simulation *sim, int &x, int &y, int &w, int &h, bool colorize = true, bool *v95 = nullptr);
std::pair<int, Type> split();
String getDisplayText(Simulation *sim, int &x, int &y, int &w, int &h, bool colorize = true, bool *v95 = nullptr) const;
std::pair<int, Type> split() const;
};
#endif