Added FloatNode and smart save/load of definition tree

Node children are now saved with their name, and if a child is not found,
it is skipped. This will allow for backward/forward compatibility of saves.
This commit is contained in:
Michaël Lemaire 2015-08-13 23:46:50 +02:00
parent 6062c755b5
commit 0fc10fd28b
15 changed files with 319 additions and 34 deletions

View file

@ -1,5 +1,6 @@
#include "DefinitionNode.h"
#include "Logs.h"
#include "PackStream.h"
DefinitionNode::DefinitionNode(DefinitionNode* parent, const std::string &name):
@ -72,20 +73,57 @@ std::string DefinitionNode::toString(int indent) const
void DefinitionNode::save(PackStream* stream) const
{
stream->write(name);
int children_count = (int)children.size();
stream->write(&children_count);
for (auto child: children)
{
stream->write(child->name);
int child_size = child->getStreamSize();
if (child_size >= 0)
{
stream->write(&child_size);
child->save(stream);
}
else
{
// Child size not known, write it to a temporary stream to know it
Logs::debug() << "Unknown size for child " << child->name << ", unefficient writing to temporary stream" << std::endl;
PackStream substream;
child->save(&substream);
stream->writeFromBuffer(substream, true);
}
}
}
void DefinitionNode::load(PackStream* stream)
{
name = stream->readString();
for (auto child: children)
int children_count;
stream->read(&children_count);
for (int i = 0; i < children_count; i++)
{
std::string child_name = stream->readString();
int child_size;
stream->read(&child_size);
DefinitionNode *child = findChildByName(child_name);
if (child)
{
// TODO type check
child->load(stream);
}
else
{
// TODO Ask subclass if it can instanciate a child
// Else skip length of unknown child
stream->skipBytes(child_size);
Logs::warning() << "Skipped unknown child '" << child_name << "'" << std::endl;
}
}
}
void DefinitionNode::copy(DefinitionNode* destination) const
@ -122,6 +160,23 @@ void DefinitionNode::removeChild(DefinitionNode* child)
}
else
{
qWarning("Trying to remove not found child '%s' from '%s'", child->name.c_str(), name.c_str());
Logs::warning() << "Trying to remove not found child '" << child->name << "' from '" << name << "'" << std::endl;
}
}
DefinitionNode *DefinitionNode::findChildByName(const std::string name)
{
for (auto child: children)
{
if (child->name == name)
{
return child;
}
}
return NULL;
}
int DefinitionNode::getStreamSize() const
{
return -1;
}

View file

@ -38,6 +38,15 @@ public:
protected:
void addChild(DefinitionNode* child);
void removeChild(DefinitionNode* child);
virtual DefinitionNode *findChildByName(const std::string name);
/**
* Get the size in bytes this child will consume when serialized to a stream.
*
* Return -1 if it can't be known. In this case, the saving will be done in a temporary
* stream to know the exact size, which will not be very efficient.
*/
int getStreamSize() const;
private:
DefinitionNode* parent;

View file

@ -0,0 +1,35 @@
#include "FloatNode.h"
#include "PackStream.h"
#include <sstream>
FloatNode::FloatNode(DefinitionNode* parent, const std::string &name, double value):
DefinitionNode(parent, name), value(value)
{
}
std::string FloatNode::toString(int indent) const
{
std::ostringstream stream;
stream << DefinitionNode::toString(indent) << " " << value;
return stream.str();
}
void FloatNode::save(PackStream *stream) const
{
stream->write(&value);
}
void FloatNode::load(PackStream *stream)
{
stream->read(&value);
}
void FloatNode::copy(DefinitionNode *destination) const
{
// TODO type check
((FloatNode *)destination)->value = value;
}

View file

@ -0,0 +1,32 @@
#ifndef FLOATNODE_H
#define FLOATNODE_H
#include "definition_global.h"
#include "DefinitionNode.h"
namespace paysages {
namespace definition {
/**
* Node with a single floating point numeric value, for the definition tree.
*/
class DEFINITIONSHARED_EXPORT FloatNode: public DefinitionNode
{
public:
FloatNode(DefinitionNode* parent, const std::string &name, double value = 0.0);
inline double getValue() const {return value;}
virtual std::string toString(int indent) const override;
virtual void save(PackStream* stream) const override;
virtual void load(PackStream* stream) override;
virtual void copy(DefinitionNode* destination) const override;
private:
double value;
};
}
}
#endif // FLOATNODE_H

View file

@ -21,7 +21,11 @@ void Layers::save(PackStream *stream) const
int layer_count = (int)layers.size();
stream->write(&layer_count);
DefinitionNode::save(stream);
for (int i = 0; i < layer_count; i++)
{
stream->write(layers[i]->getName());
layers[i]->save(stream);
}
}
void Layers::load(PackStream *stream)
@ -36,10 +40,13 @@ void Layers::load(PackStream *stream)
clear();
for (int i = 0; i < layer_count; i++)
{
addLayer();
int position = addLayer();
if (position >= 0)
{
layers[position]->setName(stream->readString());
layers[position]->load(stream);
}
}
DefinitionNode::load(stream);
}
void Layers::copy(DefinitionNode* destination_) const
@ -171,3 +178,26 @@ void Layers::clear()
removeLayer(0);
}
}
DefinitionNode *Layers::findChildByName(const std::string name)
{
DefinitionNode *result = DefinitionNode::findChildByName(name);
if (result)
{
return result;
}
else
{
int position = addLayer();
if (position >= 0)
{
result = getLayer(position);
result->setName(name);
return result;
}
else
{
return NULL;
}
}
}

View file

@ -45,6 +45,9 @@ public:
void moveLayer(DefinitionNode* layer, int new_position);
void clear();
protected:
virtual DefinitionNode *findChildByName(const std::string name) override;
public:
LayerConstructor layer_constructor;
int max_layer_count;

View file

@ -3,6 +3,7 @@
#include "TerrainHeightMap.h"
#include "NoiseGenerator.h"
#include "PackStream.h"
#include "FloatNode.h"
TerrainDefinition::TerrainDefinition(DefinitionNode* parent):
DefinitionNode(parent, "terrain")
@ -15,6 +16,7 @@ TerrainDefinition::TerrainDefinition(DefinitionNode* parent):
addChild(height_map);
water_height = -0.3;
_water_height = new FloatNode(this, "water_height", -0.3);
_height_noise = new NoiseGenerator;
}

View file

@ -52,6 +52,9 @@ public:
NoiseGenerator* _height_noise;
double _min_height;
double _max_height;
private:
FloatNode *_water_height;
};
}

View file

@ -30,7 +30,8 @@ SOURCES += \
PaintedGrid.cpp \
PaintedGridBrush.cpp \
PaintedGridData.cpp \
DefinitionNode.cpp
DefinitionNode.cpp \
FloatNode.cpp
HEADERS +=\
definition_global.h \
@ -50,7 +51,8 @@ HEADERS +=\
PaintedGrid.h \
PaintedGridBrush.h \
PaintedGridData.h \
DefinitionNode.h
DefinitionNode.h \
FloatNode.h
unix:!symbian {
maemo5 {

View file

@ -14,6 +14,7 @@
namespace paysages {
namespace definition {
class DefinitionNode;
class FloatNode;
class Scenery;
class CameraDefinition;
class SurfaceMaterial;

View file

@ -1,5 +1,6 @@
#include "PackStream.h"
#include "Logs.h"
#include <QFile>
#include <QDataStream>
#include <QString>
@ -7,15 +8,15 @@
PackStream::PackStream()
{
file = NULL;
stream = NULL;
buffer = new QByteArray();
stream = new QDataStream(buffer, QIODevice::WriteOnly);
stream->setVersion(QDataStream::Qt_5_2);
}
PackStream::~PackStream()
{
if (stream)
{
delete buffer;
delete stream;
}
if (file)
{
delete file;
@ -24,7 +25,7 @@ PackStream::~PackStream()
bool PackStream::bindToFile(const std::string &filepath, bool write)
{
if (not file and not stream)
if (not file)
{
file = new QFile(QString::fromStdString(filepath));
if (not file->open(write ? QIODevice::WriteOnly : QIODevice::ReadOnly))
@ -32,14 +33,28 @@ bool PackStream::bindToFile(const std::string &filepath, bool write)
return false;
}
stream = new QDataStream(file);
QDataStream *new_stream = new QDataStream(file);
if (new_stream)
{
delete stream;
stream = new_stream;
stream->setVersion(QDataStream::Qt_5_2);
return true;
}
else
{
return false;
}
}
else
{
return false;
}
return stream != NULL;
}
void PackStream::write(const int *value)
{
if (stream and value)
if (value)
{
*stream << *value;
}
@ -47,7 +62,7 @@ void PackStream::write(const int *value)
void PackStream::write(const double *value)
{
if (stream and value)
if (value)
{
*stream << *value;
}
@ -55,7 +70,7 @@ void PackStream::write(const double *value)
void PackStream::write(const char *value, int max_length)
{
if (stream and value)
if (value)
{
int length = qstrlen(value);
*stream << QString::fromUtf8(value, length > max_length ? max_length : length);
@ -63,16 +78,30 @@ void PackStream::write(const char *value, int max_length)
}
void PackStream::write(const std::string &value)
{
if (stream)
{
*stream << QString::fromStdString(value);
}
void PackStream::writeFromBuffer(const PackStream &other, bool prepend_size)
{
if (other.file)
{
Logs::error() << "Try to write from a substream bound to a file: " << other.file->fileName().toStdString() << std::endl;
}
else
{
if (prepend_size)
{
int buffer_size = (int)other.buffer->size();
write(&buffer_size);
}
stream->writeRawData(other.buffer->data(), other.buffer->size());
}
}
void PackStream::read(int* value)
{
if (stream and value and not stream->atEnd())
if (value and not stream->atEnd())
{
int output;
*stream >> output;
@ -82,7 +111,7 @@ void PackStream::read(int* value)
void PackStream::read(double* value)
{
if (stream and value and not stream->atEnd())
if (value and not stream->atEnd())
{
double output;
*stream >> output;
@ -92,7 +121,7 @@ void PackStream::read(double* value)
void PackStream::read(char* value, int max_length)
{
if (stream and value and not stream->atEnd())
if (value and not stream->atEnd())
{
QString output;
*stream >> output;
@ -103,7 +132,7 @@ void PackStream::read(char* value, int max_length)
std::string PackStream::readString()
{
if (stream and not stream->atEnd())
if (not stream->atEnd())
{
QString output;
*stream >> output;
@ -124,3 +153,8 @@ void PackStream::skip(const double &value, int count)
{
stream->skipRawData(sizeof(value) * count);
}
void paysages::system::PackStream::skipBytes(int bytes)
{
stream->skipRawData(bytes);
}

View file

@ -27,6 +27,15 @@ public:
void write(const char *value, const int max_length);
void write(const std::string &value);
/**
* Write the contents of another stream into this one.
*
* The other stream must not have been bound to a file (still a memory buffer).
*
* If *prepend_size* is true, an integer will be written on front with the number of bytes written.
*/
void writeFromBuffer(const PackStream &other, bool prepend_size = false);
void read(int *value);
void read(double *value);
void read(char *value, int max_length);
@ -34,9 +43,11 @@ public:
void skip(const int &value, int count=1);
void skip(const double &value, int count=1);
void skipBytes(int bytes);
private:
QFile *file;
QByteArray *buffer;
QDataStream *stream;
};

View file

@ -1,6 +1,8 @@
#include "BaseTestCase.h"
#include "DefinitionNode.h"
#include "FloatNode.h"
#include "PackStream.h"
TEST(DefinitionNode, toString)
{
@ -33,3 +35,40 @@ TEST(DefinitionNode, attachDetach)
delete root;
}
TEST(DefinitionNode, saveLoad)
{
PackStream *stream;
DefinitionNode* before = new DefinitionNode(NULL, "root");
DefinitionNode* before1 = new DefinitionNode(before, "before1");
FloatNode* before11 = new FloatNode(before1, "before11", 2.1);
FloatNode* before12 = new FloatNode(before1, "before12", -4.3);
DefinitionNode* before2 = new DefinitionNode(before, "before2");
DefinitionNode* before21 = new DefinitionNode(before2, "before21");
FloatNode* before22 = new FloatNode(before2, "before22");
FloatNode* before3 = new FloatNode(before, "before3", 6.7);
stream = new PackStream();
stream->bindToFile("/tmp/test_paysages_pack", true);
before->save(stream);
delete stream;
// Same definition tree, but with missing nodes, and added ones
DefinitionNode* after = new DefinitionNode(NULL, "root");
DefinitionNode* after1 = new DefinitionNode(after, "before1");
FloatNode* after12 = new FloatNode(after1, "before12");
FloatNode* after13 = new FloatNode(after1, "before13");
FloatNode* after3 = new FloatNode(after, "before3");
FloatNode* after4 = new FloatNode(after, "before4");
stream = new PackStream();
stream->bindToFile("/tmp/test_paysages_pack");
after->load(stream);
delete stream;
EXPECT_DOUBLE_EQ(-4.3, after12->getValue());
EXPECT_DOUBLE_EQ(0.0, after13->getValue());
EXPECT_DOUBLE_EQ(6.7, after3->getValue());
EXPECT_DOUBLE_EQ(0.0, after4->getValue());
}

View file

@ -77,3 +77,31 @@ TEST(Layers, maxLayerCount)
layers1.addLayer();
EXPECT_EQ(2, layers1.count());
}
TEST(Layers, saveLoad)
{
PackStream *stream;
Layers layers1(NULL, "test", _construc1);
layers1.addLayer();
layers1.addLayer();
ASSERT_EQ(2, layers1.count());
layers1.getLayer(0)->setName("first");
layers1.getLayer(1)->setName("second");
stream = new PackStream();
stream->bindToFile("/tmp/test_paysages_pack", true);
layers1.save(stream);
delete stream;
Layers layers2(NULL, "test", _construc1);
stream = new PackStream();
stream->bindToFile("/tmp/test_paysages_pack");
layers2.load(stream);
delete stream;
ASSERT_EQ(2, layers2.count());
EXPECT_EQ("first", layers2.getLayer(0)->getName());
EXPECT_EQ("second", layers2.getLayer(1)->getName());
}

View file

@ -1,6 +1,7 @@
#include "BaseTestCase.h"
#include "Scenery.h"
#include "Logs.h"
TEST(Scenery, saveGlobal)
{