Minor changes to improve the UserData::Variant/Properties API

Mainly added ways to forward operator= and the default copy
constructor to the std::variant so we don't have to assign values
creating new Variant{}s.
This commit is contained in:
David Capello 2022-12-30 13:50:17 -03:00
parent 1830e5343f
commit dc0e57728a
2 changed files with 115 additions and 108 deletions

View File

@ -1,4 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2022 Igara Studio S.A.
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
@ -15,6 +16,7 @@
#include "gfx/rect.h"
#include <map>
#include <stdexcept>
#include <string>
#include <variant>
#include <vector>
@ -26,26 +28,40 @@ namespace doc {
struct Fixed {
fixmath::fixed value;
};
struct VariantStruct;
using Variant = VariantStruct;
struct Variant;
using Vector = std::vector<Variant>;
using Properties = std::map<std::string, Variant>;
struct VariantStruct : std::variant<bool,
int8_t, uint8_t,
int16_t, uint16_t,
int32_t, uint32_t,
int64_t, uint64_t,
Fixed,
std::string,
gfx::Point,
gfx::Size,
gfx::Rect,
std::vector<Variant>,
Properties>{
using PropertiesMaps = std::map<std::string, Properties>;
using VariantBase = std::variant<bool,
int8_t, uint8_t,
int16_t, uint16_t,
int32_t, uint32_t,
int64_t, uint64_t,
Fixed,
std::string,
gfx::Point,
gfx::Size,
gfx::Rect,
Vector,
Properties>;
struct Variant : public VariantBase {
Variant() = default;
Variant(const Variant& v) = default;
template<typename T>
Variant(T&& v) : VariantBase(std::forward<T>(v)) { }
template<typename T>
Variant& operator=(T&& v) {
VariantBase::operator=(std::forward<T>(v));
return *this;
}
const uint16_t type() const {
return index() + 1;
}
};
using PropertiesMaps = std::map<std::string, Properties>;
UserData() : m_color(0) {
}
@ -58,7 +74,7 @@ namespace doc {
const std::string& text() const { return m_text; }
color_t color() const { return m_color; }
const PropertiesMaps& propertiesMaps() const { return m_propertiesMaps; }
Properties& properties() { return properties(""); }
Properties& properties() { return properties(std::string()); }
Properties& properties(const std::string& groupKey) { return m_propertiesMaps[groupKey]; }
void setText(const std::string& text) { m_text = text; }
@ -79,6 +95,26 @@ namespace doc {
PropertiesMaps m_propertiesMaps;
};
// macOS 10.9 C++ runtime doesn't support std::get<T>(value)
// directly and we have to use std::get_if<T>(value)
//
// TODO replace this with std::get() in the future when we drop macOS 10.9 support
template<typename T>
inline const T& get_value(const UserData::Variant& variant) {
const T* value = std::get_if<T>(&variant);
if (value == nullptr)
throw std::runtime_error("bad_variant_access");
return *value;
}
template<typename T>
inline T& get_value(UserData::Variant& variant) {
T* value = std::get_if<T>(&variant);
if (value == nullptr)
throw std::runtime_error("bad_variant_access");
return *value;
}
} // namespace doc
#endif

View File

@ -15,6 +15,7 @@
using namespace doc;
using Variant = UserData::Variant;
using Fixed = UserData::Fixed;
using Vector = UserData::Vector;
using Properties = UserData::Properties;
TEST(CustomProperties, SimpleProperties)
@ -23,110 +24,85 @@ TEST(CustomProperties, SimpleProperties)
// Initial data doesn't have any properties
EXPECT_TRUE(data.properties().empty());
data.properties()["boolean"] = Variant{false};
data.properties()["char"] = Variant{uint8_t('A')};
data.properties()["number16"] = Variant{int16_t(-1024)};
data.properties()["number32"] = Variant{int32_t(-5628102)};
data.properties()["text"] = Variant{std::string("this is some text")};
data.properties()["boolean"] = false;
data.properties()["char"] = uint8_t('A');
data.properties()["number16"] = int16_t(-1024);
data.properties()["number32"] = int32_t(-5628102);
data.properties()["text"] = std::string("this is some text");
EXPECT_TRUE(data.properties().size() == 5);
bool* boolean = std::get_if<bool>(&data.properties()["boolean"]);
EXPECT_TRUE(boolean);
EXPECT_FALSE(*boolean);
bool boolean = get_value<bool>(data.properties()["boolean"]);
EXPECT_FALSE(boolean);
uint8_t* charac = std::get_if<uint8_t>(&data.properties()["char"]);
EXPECT_TRUE(charac);
EXPECT_TRUE(*charac == 'A');
uint8_t charac = get_value<uint8_t>(data.properties()["char"]);
EXPECT_EQ('A', charac);
int16_t* number16 = std::get_if<int16_t>(&data.properties()["number16"]);
EXPECT_TRUE(number16);
EXPECT_TRUE(*number16 == -1024);
int16_t number16 = get_value<int16_t>(data.properties()["number16"]);
EXPECT_EQ(-1024, number16);
int32_t* number32 = std::get_if<int32_t>(&data.properties()["number32"]);
EXPECT_TRUE(number32);
EXPECT_TRUE(*number32 == -5628102);
int32_t number32 = get_value<int32_t>(data.properties()["number32"]);
EXPECT_EQ(-5628102, number32);
std::string* text = std::get_if<std::string>(&data.properties()["text"]);
EXPECT_TRUE(text);
EXPECT_TRUE(*text == "this is some text");
std::string text = get_value<std::string>(data.properties()["text"]);
EXPECT_EQ("this is some text", text);
}
TEST(CustomProperties, ComplexProperties)
{
UserData data;
// Add a vector of ints as the "list" custom property.
data.properties()["list"] = Variant{
std::vector<Variant>{
Variant{1},
Variant{2},
Variant{3}
}
};
data.properties()["list"] = Vector{ 1, 2, 3 };
// Check that we have one property
EXPECT_TRUE(data.properties().size() == 1);
// Get the "list" vector and check some of its content
std::vector<Variant>* list = std::get_if<std::vector<Variant>>(&data.properties()["list"]);
EXPECT_TRUE(list);
EXPECT_TRUE(list->size() == 3);
int* v1 = std::get_if<int>(&(*list)[1]);
EXPECT_TRUE(*v1 == 2);
auto list = get_value<Vector>(data.properties()["list"]);
EXPECT_TRUE(list.size() == 3);
int v1 = get_value<int>(list[1]);
EXPECT_EQ(2, v1);
// Add Point, Size, and Rect properties
data.properties()["point"] = Variant{gfx::Point(10,30)};
data.properties()["size"] = Variant{gfx::Size(50,20)};
data.properties()["rect"] = Variant{gfx::Rect(11,22,33,44)};
EXPECT_TRUE(data.properties().size() == 4);
data.properties()["point"] = gfx::Point(10,30);
data.properties()["size"] = gfx::Size(50,20);
data.properties()["rect"] = gfx::Rect(11,22,33,44);
EXPECT_EQ(4, data.properties().size());
gfx::Point* point = std::get_if<gfx::Point>(&data.properties()["point"]);
EXPECT_TRUE(point);
EXPECT_TRUE(point->x == 10 && point->y == 30);
gfx::Point point = get_value<gfx::Point>(data.properties()["point"]);
EXPECT_TRUE(point.x == 10 && point.y == 30);
gfx::Size* size = std::get_if<gfx::Size>(&data.properties()["size"]);
EXPECT_TRUE(size);
EXPECT_TRUE(size->w == 50 && size->h == 20);
gfx::Size size = get_value<gfx::Size>(data.properties()["size"]);
EXPECT_TRUE(size.w == 50 && size.h == 20);
gfx::Rect* rect = std::get_if<gfx::Rect>(&data.properties()["rect"]);
EXPECT_TRUE(rect);
EXPECT_TRUE(rect->x == 11 && rect->y == 22);
EXPECT_TRUE(rect->w == 33 && rect->h == 44);
gfx::Rect rect = get_value<gfx::Rect>(data.properties()["rect"]);
EXPECT_TRUE(rect.x == 11 && rect.y == 22);
EXPECT_TRUE(rect.w == 33 && rect.h == 44);
// Add Fixed property
data.properties()["fixed"] = Variant{Fixed{fixmath::ftofix(10.5)}};
EXPECT_TRUE(data.properties().size() == 5);
data.properties()["fixed"] = Fixed{fixmath::ftofix(10.5)};
EXPECT_EQ(5, data.properties().size());
Fixed* fixed = std::get_if<Fixed>(&data.properties()["fixed"]);
EXPECT_TRUE(fixed);
EXPECT_TRUE(fixed->value == fixmath::ftofix(10.5));
Fixed fixed = get_value<Fixed>(data.properties()["fixed"]);
EXPECT_EQ(fixmath::ftofix(10.5), fixed.value);
// Add an object with Properties
data.properties()["object"] = Variant{
Properties{
{"id", Variant{uint16_t(400)}},
{"color", Variant{std::string("red")}},
{"size", Variant{gfx::Size{25,65}}}
}
};
data.properties()["object"] = Properties{ { "id", uint16_t(400) },
{ "color", std::string("red") },
{ "size", gfx::Size{ 25, 65 } } };
EXPECT_TRUE(data.properties().size() == 6);
Properties* object = std::get_if<Properties>(&data.properties()["object"]);
EXPECT_TRUE(object);
Properties object = get_value<Properties>(data.properties()["object"]);
uint16_t id = get_value<uint16_t>(object["id"]);
EXPECT_EQ(400, id);
uint16_t* id = std::get_if<uint16_t>(&(*object)["id"]);
EXPECT_TRUE(id);
EXPECT_TRUE(*id == 400);
std::string color = get_value<std::string>(object["color"]);
EXPECT_EQ("red", color);
std::string* color = std::get_if<std::string>(&(*object)["color"]);
EXPECT_TRUE(color);
EXPECT_TRUE(*color == "red");
size = std::get_if<gfx::Size>(&(*object)["size"]);
EXPECT_TRUE(size);
EXPECT_TRUE(size->w == 25 && size->h == 65);
size = get_value<gfx::Size>(object["size"]);
EXPECT_TRUE(size.w == 25 && size.h == 65);
// Try to get wrong type
gfx::Point* p = std::get_if<gfx::Point>(&data.properties()["rect"]);
EXPECT_TRUE(p == nullptr);
EXPECT_EQ(nullptr, p);
data.properties().erase("rect");
data.properties().erase("list");
@ -134,7 +110,7 @@ TEST(CustomProperties, ComplexProperties)
data.properties().erase("size");
data.properties().erase("object");
data.properties().erase("fixed");
EXPECT_TRUE(data.properties().size() == 0);
EXPECT_EQ(0, data.properties().size());
}
TEST(ExtensionProperties, SimpleProperties)
@ -143,33 +119,28 @@ TEST(ExtensionProperties, SimpleProperties)
EXPECT_TRUE(data.properties().empty());
EXPECT_TRUE(data.properties("someExtensionId").empty());
data.properties("someExtensionId")["boolean"] = Variant{false};
data.properties("someExtensionId")["char"] = Variant{uint8_t('A')};
data.properties("someExtensionId")["number16"] = Variant{int16_t(-1024)};
data.properties()["number32"] = Variant{int32_t(-5628102)};
data.properties()["text"] = Variant{std::string("this is some text")};
data.properties("someExtensionId")["boolean"] = false;
data.properties("someExtensionId")["char"] = uint8_t('A');
data.properties("someExtensionId")["number16"] = int16_t(-1024);
data.properties()["number32"] = int32_t(-5628102);
data.properties()["text"] = std::string("this is some text");
EXPECT_TRUE(data.properties("someExtensionId").size() == 3);
EXPECT_TRUE(data.properties().size() == 2);
bool* boolean = std::get_if<bool>(&data.properties("someExtensionId")["boolean"]);
EXPECT_TRUE(boolean);
EXPECT_FALSE(*boolean);
auto boolean = get_value<bool>(data.properties("someExtensionId")["boolean"]);
EXPECT_FALSE(boolean);
uint8_t* charac = std::get_if<uint8_t>(&data.properties("someExtensionId")["char"]);
EXPECT_TRUE(charac);
EXPECT_TRUE(*charac == 'A');
auto charac = get_value<uint8_t>(data.properties("someExtensionId")["char"]);
EXPECT_EQ('A', charac);
int16_t* number16 = std::get_if<int16_t>(&data.properties("someExtensionId")["number16"]);
EXPECT_TRUE(number16);
EXPECT_TRUE(*number16 == -1024);
auto number16 = get_value<int16_t>(data.properties("someExtensionId")["number16"]);
EXPECT_EQ(-1024, number16);
int32_t* number32 = std::get_if<int32_t>(&data.properties()["number32"]);
EXPECT_TRUE(number32);
EXPECT_TRUE(*number32 == -5628102);
auto number32 = get_value<int32_t>(data.properties()["number32"]);
EXPECT_EQ(-5628102, number32);
std::string* text = std::get_if<std::string>(&data.properties()["text"]);
EXPECT_TRUE(text);
EXPECT_TRUE(*text == "this is some text");
auto text = get_value<std::string>(data.properties()["text"]);
EXPECT_EQ("this is some text", text);
data.properties().erase("number32");
data.properties().erase("text");