Browse Source

Add serialization for floating point types

- Add fixed serialization fro float and double
- Make generation for float and double fields explicit, was qreal
Alexey Edelev 6 years ago
parent
commit
fa585ba8c3
4 changed files with 172 additions and 21 deletions
  1. 2 2
      src/generator/templates.h
  2. 41 11
      src/lib/protobufobject.h
  3. 121 0
      tests/serializationtest.cpp
  4. 8 8
      tests/simpletest.cpp

+ 2 - 2
src/generator/templates.h

@@ -101,8 +101,8 @@ static const char *SemicolonBlockEnclosureTemplate = "};\n";
 static const char *EmptyBlockTemplate = "{}\n\n";
 
 static const std::unordered_map<::google::protobuf::FieldDescriptor::Type, std::string> TypeReflection = {
-    {::google::protobuf::FieldDescriptor::TYPE_DOUBLE, "qreal"},
-    {::google::protobuf::FieldDescriptor::TYPE_FLOAT, "qreal"},
+    {::google::protobuf::FieldDescriptor::TYPE_DOUBLE, "double"},
+    {::google::protobuf::FieldDescriptor::TYPE_FLOAT, "float"},
     //        {FieldDescriptor::TYPE_INT64, "int"},//Not supported see https://doc.qt.io/qt-5/qtqml-typesystem-basictypes.html
     //        {FieldDescriptor::TYPE_UINT64,"int"},//Not supported see https://doc.qt.io/qt-5/qtqml-typesystem-basictypes.html
     {::google::protobuf::FieldDescriptor::TYPE_INT32, "int"},

+ 41 - 11
src/lib/protobufobject.h

@@ -34,6 +34,8 @@
 #include <unordered_map>
 #include <type_traits>
 
+#define ASSERT_FIELD_NUMBER(X) Q_ASSERT_X(X < 128 && X > 0, T::staticMetaObject.className(), "fieldIndex is out of range")
+
 namespace qtprotobuf {
 
 enum WireTypes {
@@ -53,14 +55,20 @@ public:
     QByteArray serialize() {
         QByteArray result;
         T* instance = dynamic_cast<T*>(this);
-        for(auto field : T::propertyOrdering) {
+        for (auto field : T::propertyOrdering) {
             int propertyIndex = field.second;
             int fieldIndex = field.first;
             const char* propertyName = T::staticMetaObject.property(propertyIndex).name();
-            switch(T::staticMetaObject.property(propertyIndex).type()) {
-            case QVariant::Int:
+            switch (T::staticMetaObject.property(propertyIndex).type()) {
+            case QMetaType::Int:
                 result.append(serializeVarint(instance->property(propertyName).toInt(), fieldIndex));
                 break;
+            case QMetaType::Float:
+                result.append(serializeFixed(instance->property(propertyName).toFloat(), fieldIndex));
+                break;
+            case QMetaType::Double:
+                result.append(serializeFixed(instance->property(propertyName).toDouble(), fieldIndex));
+                break;
             }
         }
 
@@ -72,9 +80,27 @@ public:
         //TODO
     }
 
+    template <typename V,
+              typename std::enable_if_t<std::is_floating_point<V>::value, int> = 0>
+    QByteArray serializeFixed(V value, int fieldIndex) {
+        ASSERT_FIELD_NUMBER(fieldIndex);
+
+        //Reserve required amount of bytes
+        QByteArray result(sizeof(V) + 1, '\0');
+
+        //Undentify exact wiretype
+        constexpr WireTypes wireType = sizeof(V) == 4 ? Fixed32 : Fixed64;
+        unsigned char typeByte = getTypeByte(fieldIndex, wireType);
+        result[0] = *(char *)&typeByte;
+        *(V*)&(result.data()[1]) = value;
+        return result;
+    }
+
     template <typename V,
               typename std::enable_if_t<std::is_signed<V>::value, int> = 0>
     QByteArray serializeVarint(V value, int fieldIndex) {
+        ASSERT_FIELD_NUMBER(fieldIndex);
+
         using UV = typename std::make_unsigned<V>::type;
         //Use ZigZag convertion first and apply unsigned variant next
         value = (value << 1) ^ (value >> (sizeof(UV) * 8 - 1));
@@ -85,21 +111,16 @@ public:
     template <typename V,
               typename std::enable_if_t<std::is_unsigned<V>::value, int> = 0>
     QByteArray serializeVarint(V value, int fieldIndex) {
-        /*  Header byte
-         *  bits    | 7  6  5  4  3 |  2  1  0
-         *  -----------------------------------
-         *  meaning |  Field index  |   Type
-         */
-        Q_ASSERT_X(fieldIndex < 128 && fieldIndex > 0, T::staticMetaObject.className(), "fieldIndex is out of range");
+        ASSERT_FIELD_NUMBER(fieldIndex);
 
-        unsigned char typeByte = (fieldIndex << 3) | Varint;
+        unsigned char typeByte = getTypeByte(fieldIndex, Varint);
         QByteArray result;
         //Reserve maximum required amount of bytes
         result.reserve(sizeof(V) + 1);
         //Put type byte at beginning
         result.append(*(char *)&typeByte);
 
-        while(value > 0) {
+        while (value > 0) {
             //Put first 7 bits to result buffer and mark as not last
             result.append(value & 0x7F | 0x80);
             //Devide values to chunks of 7 bits, move to next chunk
@@ -115,6 +136,15 @@ public:
         result.data()[result.size() - 1] &= ~0x80;
         return result;
     }
+
+    inline unsigned char getTypeByte(int fieldIndex, WireTypes wireType) {
+        /*  Header byte
+         *  bits    | 7  6  5  4  3 |  2  1  0
+         *  -----------------------------------
+         *  meaning |  Field index  |   Type
+         */
+         return (fieldIndex << 3) | wireType;
+    }
 };
 
 }

+ 121 - 0
tests/serializationtest.cpp

@@ -26,6 +26,8 @@
 #include "serializationtest.h"
 
 #include "simpleintmessage.h"
+#include "simplefloatmessage.h"
+#include "simpledoublemessage.h"
 
 using namespace qtprotobuf::tests;
 
@@ -166,3 +168,122 @@ TEST_F(SerializationTest, IntMessageSrializeTest)
     ASSERT_EQ(result.at(3), '\x04');
 }
 
+TEST_F(SerializationTest, FloatMessageSrializeTest)
+{
+    constexpr int FloatMessageSize = 5;
+    SimpleFloatMessage test;
+    test.setTestFieldFloat(0.1f);
+    QByteArray result = test.serialize();
+    ASSERT_EQ(result.size(), FloatMessageSize);
+    ASSERT_EQ(result.at(0), 0x3d);
+    ASSERT_EQ(result.at(1), '\xcd');
+    ASSERT_EQ(result.at(2), '\xcc');
+    ASSERT_EQ(result.at(3), '\xcc');
+    ASSERT_EQ(result.at(4), '\x3d');
+
+    test.setTestFieldFloat(FLT_MIN);
+    result = test.serialize();
+    ASSERT_EQ(result.size(), FloatMessageSize);
+    ASSERT_EQ(result.at(0), 0x3d);
+    ASSERT_EQ(result.at(1), '\x00');
+    ASSERT_EQ(result.at(2), '\x00');
+    ASSERT_EQ(result.at(3), '\x80');
+    ASSERT_EQ(result.at(4), '\x00');
+
+    test.setTestFieldFloat(FLT_MAX);
+    result = test.serialize();
+    ASSERT_EQ(result.size(), FloatMessageSize);
+    ASSERT_EQ(result.at(0), 0x3d);
+    ASSERT_EQ(result.at(1), '\xff');
+    ASSERT_EQ(result.at(2), '\xff');
+    ASSERT_EQ(result.at(3), '\x7f');
+    ASSERT_EQ(result.at(4), '\x7f');
+
+    test.setTestFieldFloat(-4.2f);
+    result = test.serialize();
+    ASSERT_EQ(result.size(), FloatMessageSize);
+    ASSERT_EQ(result.at(0), 0x3d);
+    ASSERT_EQ(result.at(1), '\x66');
+    ASSERT_EQ(result.at(2), '\x66');
+    ASSERT_EQ(result.at(3), '\x86');
+    ASSERT_EQ(result.at(4), '\xc0');
+
+    test.setTestFieldFloat(-0.0f);
+    result = test.serialize();
+    ASSERT_EQ(result.size(), FloatMessageSize);
+    ASSERT_EQ(result.at(0), 0x3d);
+    ASSERT_EQ(result.at(1), '\x00');
+    ASSERT_EQ(result.at(2), '\x00');
+    ASSERT_EQ(result.at(3), '\x00');
+    ASSERT_EQ(result.at(4), '\x80');
+}
+
+TEST_F(SerializationTest, DoubleMessageSrializeTest)
+{
+    constexpr int DoubleMessageSize = 9;
+    SimpleDoubleMessage test;
+    test.setTestFieldDouble(0.1);
+    QByteArray result = test.serialize();
+    ASSERT_EQ(result.size(), DoubleMessageSize);
+    ASSERT_EQ(result.at(0), 0x41);
+    ASSERT_EQ(result.at(1), '\x9a');
+    ASSERT_EQ(result.at(2), '\x99');
+    ASSERT_EQ(result.at(3), '\x99');
+    ASSERT_EQ(result.at(4), '\x99');
+    ASSERT_EQ(result.at(5), '\x99');
+    ASSERT_EQ(result.at(6), '\x99');
+    ASSERT_EQ(result.at(7), '\xb9');
+    ASSERT_EQ(result.at(8), '\x3f');
+
+    test.setTestFieldDouble(DBL_MIN);
+    result = test.serialize();
+    ASSERT_EQ(result.size(), DoubleMessageSize);
+    ASSERT_EQ(result.at(0), 0x41);
+    ASSERT_EQ(result.at(1), '\x00');
+    ASSERT_EQ(result.at(2), '\x00');
+    ASSERT_EQ(result.at(3), '\x00');
+    ASSERT_EQ(result.at(4), '\x00');
+    ASSERT_EQ(result.at(5), '\x00');
+    ASSERT_EQ(result.at(6), '\x00');
+    ASSERT_EQ(result.at(7), '\x10');
+    ASSERT_EQ(result.at(8), '\x00');
+
+    test.setTestFieldDouble(DBL_MAX);
+    result = test.serialize();
+    ASSERT_EQ(result.size(), DoubleMessageSize);
+    ASSERT_EQ(result.at(0), 0x41);
+    ASSERT_EQ(result.at(1), '\xff');
+    ASSERT_EQ(result.at(2), '\xff');
+    ASSERT_EQ(result.at(3), '\xff');
+    ASSERT_EQ(result.at(4), '\xff');
+    ASSERT_EQ(result.at(5), '\xff');
+    ASSERT_EQ(result.at(6), '\xff');
+    ASSERT_EQ(result.at(7), '\xef');
+    ASSERT_EQ(result.at(8), '\x7f');
+
+    test.setTestFieldDouble(-4.2);
+    result = test.serialize();
+    ASSERT_EQ(result.size(), DoubleMessageSize);
+    ASSERT_EQ(result.at(0), 0x41);
+    ASSERT_EQ(result.at(1), '\xcd');
+    ASSERT_EQ(result.at(2), '\xcc');
+    ASSERT_EQ(result.at(3), '\xcc');
+    ASSERT_EQ(result.at(4), '\xcc');
+    ASSERT_EQ(result.at(5), '\xcc');
+    ASSERT_EQ(result.at(6), '\xcc');
+    ASSERT_EQ(result.at(7), '\x10');
+    ASSERT_EQ(result.at(8), '\xc0');
+
+    test.setTestFieldDouble(0.0);
+    result = test.serialize();
+    ASSERT_EQ(result.size(), DoubleMessageSize);
+    ASSERT_EQ(result.at(0), 0x41);
+    ASSERT_EQ(result.at(1), '\x00');
+    ASSERT_EQ(result.at(2), '\x00');
+    ASSERT_EQ(result.at(3), '\x00');
+    ASSERT_EQ(result.at(4), '\x00');
+    ASSERT_EQ(result.at(5), '\x00');
+    ASSERT_EQ(result.at(6), '\x00');
+    ASSERT_EQ(result.at(7), '\x00');
+    ASSERT_EQ(result.at(8), '\x00');
+}

+ 8 - 8
tests/simpletest.cpp

@@ -43,7 +43,7 @@ TEST_F(SimpleTest, SimpleIntMessageTest)
     const char* propertyName = "testFieldInt";
     SimpleIntMessage test;
     int propertyNumber = SimpleIntMessage::propertyOrdering.at(1); //See simpletest.proto
-    ASSERT_EQ(SimpleIntMessage::staticMetaObject.property(propertyNumber).type(), QVariant::Int);
+    ASSERT_EQ(SimpleIntMessage::staticMetaObject.property(propertyNumber).type(), QMetaType::Int);
     ASSERT_STREQ(SimpleIntMessage::staticMetaObject.property(propertyNumber).name(), propertyName);
     ASSERT_TRUE(test.setProperty(propertyName, QVariant::fromValue(1)));
     ASSERT_EQ(test.property(propertyName).toInt(), 1);
@@ -54,7 +54,7 @@ TEST_F(SimpleTest, SimpleStringMessageTest)
     const char* propertyName = "testFieldString";
     SimpleStringMessage test;
     int propertyNumber = SimpleStringMessage::propertyOrdering.at(6); //See simpletest.proto
-    ASSERT_EQ(SimpleStringMessage::staticMetaObject.property(propertyNumber).type(), QVariant::String);
+    ASSERT_EQ(SimpleStringMessage::staticMetaObject.property(propertyNumber).type(), QMetaType::QString);
     ASSERT_STREQ(SimpleStringMessage::staticMetaObject.property(propertyNumber).name(), propertyName);
     ASSERT_TRUE(test.setProperty(propertyName, QVariant::fromValue(QString("test1"))));
     ASSERT_STREQ(test.property(propertyName).toString().toStdString().c_str(), "test1");
@@ -65,10 +65,10 @@ TEST_F(SimpleTest, SimpleFloatMessageTest)
     const char* propertyName = "testFieldFloat";
     SimpleFloatMessage test;
     int propertyNumber = SimpleFloatMessage::propertyOrdering.at(7); //See simpletest.proto
-    ASSERT_EQ(SimpleFloatMessage::staticMetaObject.property(propertyNumber).type(), QVariant::Double);
+    ASSERT_EQ(SimpleFloatMessage::staticMetaObject.property(propertyNumber).type(), QMetaType::Float);
     ASSERT_STREQ(SimpleFloatMessage::staticMetaObject.property(propertyNumber).name(), "testFieldFloat");
-    ASSERT_TRUE(test.setProperty(propertyName, QVariant::fromValue(1.55)));
-    ASSERT_EQ(test.property(propertyName).toDouble(), 1.55);
+    ASSERT_TRUE(test.setProperty(propertyName, QVariant::fromValue<float>(1.55)));
+    ASSERT_TRUE(qFuzzyCompare(test.property(propertyName).toFloat(), 1.55f));
 }
 
 TEST_F(SimpleTest, SimpleDoubleMessageTest)
@@ -76,8 +76,8 @@ TEST_F(SimpleTest, SimpleDoubleMessageTest)
     const char* propertyName = "testFieldDouble";
     SimpleDoubleMessage test;
     int propertyNumber = SimpleDoubleMessage::propertyOrdering.at(8); //See simpletest.proto
-    ASSERT_EQ(SimpleDoubleMessage::staticMetaObject.property(propertyNumber).type(), QVariant::Double);
+    ASSERT_EQ(SimpleDoubleMessage::staticMetaObject.property(propertyNumber).type(), QMetaType::Double);
     ASSERT_STREQ(SimpleDoubleMessage::staticMetaObject.property(propertyNumber).name(), propertyName);
-    ASSERT_TRUE(test.setProperty(propertyName, QVariant::fromValue(0.55)));
-    ASSERT_EQ(test.property(propertyName).toDouble(), 0.55);
+    ASSERT_TRUE(test.setProperty(propertyName, QVariant::fromValue<double>(0.55)));
+    ASSERT_FLOAT_EQ(test.property(propertyName).toDouble(), 0.55);
 }