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:
parent
6062c755b5
commit
0fc10fd28b
15 changed files with 319 additions and 34 deletions
|
@ -1,5 +1,6 @@
|
|||
#include "DefinitionNode.h"
|
||||
|
||||
#include "Logs.h"
|
||||
#include "PackStream.h"
|
||||
|
||||
DefinitionNode::DefinitionNode(DefinitionNode* parent, const std::string &name):
|
||||
|
@ -72,19 +73,56 @@ 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)
|
||||
{
|
||||
child->save(stream);
|
||||
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++)
|
||||
{
|
||||
child->load(stream);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
35
src/definition/FloatNode.cpp
Normal file
35
src/definition/FloatNode.cpp
Normal 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;
|
||||
}
|
32
src/definition/FloatNode.h
Normal file
32
src/definition/FloatNode.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,9 @@ public:
|
|||
NoiseGenerator* _height_noise;
|
||||
double _min_height;
|
||||
double _max_height;
|
||||
|
||||
private:
|
||||
FloatNode *_water_height;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
namespace paysages {
|
||||
namespace definition {
|
||||
class DefinitionNode;
|
||||
class FloatNode;
|
||||
class Scenery;
|
||||
class CameraDefinition;
|
||||
class SurfaceMaterial;
|
||||
|
|
|
@ -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 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);
|
||||
|
@ -64,15 +79,29 @@ 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)
|
||||
{
|
||||
*stream << QString::fromStdString(value);
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -27,17 +27,28 @@ public:
|
|||
void write(const char *value, const int max_length);
|
||||
void write(const std::string &value);
|
||||
|
||||
void read(int* value);
|
||||
void read(double* value);
|
||||
void read(char* value, int max_length);
|
||||
/**
|
||||
* 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);
|
||||
std::string readString();
|
||||
|
||||
void skip(const int &value, int count=1);
|
||||
void skip(const double &value, int count=1);
|
||||
void skipBytes(int bytes);
|
||||
|
||||
private:
|
||||
QFile* file;
|
||||
QDataStream* stream;
|
||||
QFile *file;
|
||||
QByteArray *buffer;
|
||||
QDataStream *stream;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "BaseTestCase.h"
|
||||
|
||||
#include "Scenery.h"
|
||||
#include "Logs.h"
|
||||
|
||||
TEST(Scenery, saveGlobal)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue