Skip to content

Commit

Permalink
Merge pull request #285 from qicosmos/reflection
Browse files Browse the repository at this point in the history
  • Loading branch information
qicosmos authored May 28, 2024
2 parents 137b1fb + 0086607 commit 90e9f4a
Show file tree
Hide file tree
Showing 5 changed files with 310 additions and 57 deletions.
74 changes: 62 additions & 12 deletions iguana/pb_util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <cstddef>
#include <cstring>
#include <map>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
Expand Down Expand Up @@ -34,28 +35,77 @@ IGUANA_INLINE void to_pb(T& t, Stream& out);
template <typename T>
IGUANA_INLINE void from_pb(T& t, std::string_view pb_str);

struct pb_base {
virtual void to_pb(std::string& str) {}
virtual void from_pb(std::string_view str) {}
using base = detail::base;

size_t cache_size = 0;
virtual ~pb_base() {}
};
template <typename T, typename U>
IGUANA_INLINE constexpr size_t member_offset(T* t, U T::*member) {
return (char*)&(t->*member) - (char*)t;
}

template <typename T>
struct pb_base_impl : public pb_base {
struct base_impl : public base {
void to_pb(std::string& str) override {
iguana::to_pb(*(static_cast<T*>(this)), str);
}

void from_pb(std::string_view str) override {
iguana::from_pb(*(static_cast<T*>(this)), str);
}
virtual ~pb_base_impl() {}

iguana::detail::field_info get_field_info(std::string_view name) override {
static constexpr auto map = iguana::get_members<T>();
iguana::detail::field_info info{};
for (auto [no, field] : map) {
if (info.offset > 0) {
break;
}
std::visit(
[&](auto val) {
if (val.field_name == name) {
info.offset = member_offset((T*)this, val.member_ptr);
using value_type = typename decltype(val)::value_type;
#if defined(__clang__) || defined(_MSC_VER) || \
(defined(__GNUC__) && __GNUC__ > 8)
info.type_name = type_string<value_type>();
#endif
}
},
field);
}

return info;
}

std::vector<std::string_view> get_fields_name() override {
static constexpr auto map = iguana::get_members<T>();
std::vector<std::string_view> vec;
for (auto [no, val] : map) {
std::visit(
[&](auto& field) {
vec.push_back(std::string_view(field.field_name.data(),
field.field_name.size()));
},
val);
}
return vec;
}

virtual ~base_impl() {}

size_t cache_size = 0;
};

template <typename T>
constexpr bool inherits_from_pb_base_v = std::is_base_of_v<pb_base, T>;
constexpr bool inherits_from_base_v = std::is_base_of_v<base, T>;

IGUANA_INLINE std::shared_ptr<base> create_instance(std::string_view name) {
auto it = iguana::detail::g_pb_map.find(name);
if (it == iguana::detail::g_pb_map.end()) {
throw std::invalid_argument(std::string(name) +
"not inheried from iguana::base_impl");
}
return it->second();
}

namespace detail {
template <typename T>
Expand Down Expand Up @@ -468,7 +518,7 @@ IGUANA_INLINE size_t pb_key_value_size(Type&& t, Arr& size_arr) {
static constexpr auto tuple = get_members_tuple<T>();
constexpr size_t SIZE = std::tuple_size_v<std::decay_t<decltype(tuple)>>;
size_t pre_index = -1;
if constexpr (!inherits_from_pb_base_v<T> && key_size != 0) {
if constexpr (!inherits_from_base_v<T> && key_size != 0) {
pre_index = size_arr.size();
size_arr.push_back(0); // placeholder
}
Expand Down Expand Up @@ -497,7 +547,7 @@ IGUANA_INLINE size_t pb_key_value_size(Type&& t, Arr& size_arr) {
}
},
std::make_index_sequence<SIZE>{});
if constexpr (inherits_from_pb_base_v<T>) {
if constexpr (inherits_from_base_v<T>) {
t.cache_size = len;
}
else if constexpr (key_size != 0) {
Expand Down Expand Up @@ -566,7 +616,7 @@ template <bool skip_next = true, typename Type>
IGUANA_INLINE size_t pb_value_size(Type&& t, uint32_t*& sz_ptr) {
using T = std::remove_const_t<std::remove_reference_t<Type>>;
if constexpr (is_reflection_v<T> || is_custom_reflection_v<T>) {
if constexpr (inherits_from_pb_base_v<T>) {
if constexpr (inherits_from_base_v<T>) {
return t.cache_size;
}
else {
Expand Down
92 changes: 81 additions & 11 deletions iguana/reflection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <iomanip>
#include <iostream>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <string_view>
Expand All @@ -19,6 +20,7 @@

#include "detail/string_stream.hpp"
#include "detail/traits.hpp"
#include "enum_reflection.hpp"
#include "frozen/string.h"
#include "frozen/unordered_map.h"

Expand Down Expand Up @@ -553,7 +555,85 @@ namespace iguana::detail {
template <typename T>
struct identity {};

struct field_info {
size_t offset;
std::string_view type_name;
};

struct base {
virtual void to_pb(std::string &str) {}
virtual void from_pb(std::string_view str) {}
virtual std::vector<std::string_view> get_fields_name() { return {}; }
virtual iguana::detail::field_info get_field_info(std::string_view name) {
return {};
}

template <typename T>
T &get_field_value(std::string_view name) {
auto info = get_field_info(name);
check_field<T>(name, info);
auto ptr = (((char *)this) + info.offset);
return *((T *)ptr);
}

template <typename T, typename FiledType = T>
void set_field_value(std::string_view name, T val) {
auto info = get_field_info(name);
check_field<FiledType>(name, info);

auto ptr = (((char *)this) + info.offset);

static_assert(std::is_constructible_v<FiledType, T>, "can not assign");

*((FiledType *)ptr) = std::move(val);
}
virtual ~base() {}

private:
template <typename T>
void check_field(std::string_view name, const field_info &info) {
if (info.offset == 0) {
throw std::invalid_argument(std::string(name) + " field not exist ");
}

#if defined(__clang__) || defined(_MSC_VER) || \
(defined(__GNUC__) && __GNUC__ > 8)
if (info.type_name != iguana::type_string<T>()) {
std::string str = "type is not match: can not assign ";
str.append(iguana::type_string<T>());
str.append(" to ").append(info.type_name);

throw std::invalid_argument(str);
}
#endif
}
};

inline std::unordered_map<std::string_view,
std::function<std::shared_ptr<base>()>>
g_pb_map;

template <typename T>
inline bool register_type() {
#if defined(__clang__) || defined(_MSC_VER) || \
(defined(__GNUC__) && __GNUC__ > 8)
if constexpr (std::is_base_of_v<base, T>) {
auto it = g_pb_map.emplace(type_string<T>(), [] {
return std::make_shared<T>();
});
return it.second;
}
else {
return true;
}
#else
return true;
#endif
}

#define MAKE_META_DATA_IMPL(STRUCT_NAME, ...) \
static inline bool IGUANA_UNIQUE_VARIABLE(reg_var) = \
iguana::detail::register_type<STRUCT_NAME>(); \
[[maybe_unused]] inline static auto iguana_reflect_members( \
const iguana::detail::identity<STRUCT_NAME> &) { \
struct reflect_members { \
Expand Down Expand Up @@ -844,11 +924,7 @@ constexpr inline auto get_members() {
STRUCT_NAME, ALIAS, \
std::tuple_size_v<decltype(std::make_tuple(__VA_ARGS__))>, __VA_ARGS__)

#ifdef _MSC_VER
#define IGUANA_UNIQUE_VARIABLE(str) MACRO_CONCAT(str, __COUNTER__)
#else
#define IGUANA_UNIQUE_VARIABLE(str) MACRO_CONCAT(str, __LINE__)
#endif
template <typename T>
struct iguana_required_struct;
#define REQUIRED_IMPL(STRUCT_NAME, N, ...) \
Expand Down Expand Up @@ -903,12 +979,6 @@ inline int add_custom_fields(std::string_view key,
return 0;
}

#ifdef _MSC_VER
#define IGUANA_UNIQUE_VARIABLE(str) MACRO_CONCAT(str, __COUNTER__)
#else
#define IGUANA_UNIQUE_VARIABLE(str) MACRO_CONCAT(str, __LINE__)
#endif

#define CUSTOM_FIELDS_IMPL(STRUCT_NAME, N, ...) \
inline auto IGUANA_UNIQUE_VARIABLE(STRUCT_NAME) = iguana::add_custom_fields( \
#STRUCT_NAME, {MARCO_EXPAND(MACRO_CONCAT(CON_STR, N)(__VA_ARGS__))});
Expand Down Expand Up @@ -953,7 +1023,7 @@ struct is_reflection<T, std::enable_if_t<is_public_reflection_v<T>>>

template <typename T>
inline auto iguana_reflect_type(const T &t) {
if constexpr (is_public_reflection_v<T>) {
if constexpr (is_public_reflection_v<std::decay_t<T>>) {
return iguana_reflect_members(iguana::detail::identity<T>{});
}
else {
Expand Down
76 changes: 69 additions & 7 deletions lang/struct_pb_intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ struct_pb 是基于C++17 开发的高性能、易用、header only的protobuf格
```cpp
#include <ylt/struct_pb.hpp>

struct my_struct : struct_pb::pb_base_impl<my_struct> {
struct my_struct {
int x;
bool y;
struct_pb::fixed64_t z;
};
REFLECTION(my_struct, x, y, z);

struct nest : struct_pb::pb_base_impl<nest> {
struct nest {
std::string name;
my_struct value;
int var;
Expand All @@ -29,10 +29,10 @@ REFLECTION(nest, name, value, var);
### 序列化
```cpp
int main() {
nest v{0, "Hi", {0, 1, false, 3}, 5}, v2{};
nest v{"Hi", {1, false, {3}}, 5}, v2{};
std::string s;
struct_pb::to_pb(v, s);
struct_pb::from_pb(v2, s);
iguana::to_pb(v, s);
iguana::from_pb(v2, s);
assert(v.var == v2.var);
assert(v.value.y == v2.value.y);
assert(v.value.z == v2.value.z);
Expand All @@ -53,6 +53,68 @@ message nest {
}
```

## 动态反射
特性:
- 根据对象名称创建实例;
- 获取对象的所有字段名;
- 根据对象实例和字段名获取或设置字段的值

### 根据名称创建对象
```cpp
struct my_struct {
int x;
bool y;
iguana::fixed64_t z;
};
REFLECTION(my_struct, x, y, z);

struct nest1 : public iguana::base_imple<nest1> {
nest1() = default;
nest1(std::string s, my_struct t, int d)
: name(std::move(s)), value(t), var(d) {}
std::string name;
my_struct value;
int var;
};
REFLECTION(nest1, name, value, var);
```
```cpp
std::shared_ptr<base> t = iguana::create_instance("nest1");
```
根据对象nest1创建了实例,返回的是基类指针。

“根据对象名称创建实例” 要求对象必须从iguana::base_impl 派生,如果没有派生则创建实例会抛异常。

### 根据名称设置字段的值
```cpp
auto t = iguana::create_instance("nest1");

std::vector<std::string_view> fields_name = t->get_fields_name();
CHECK(fields_name == std::vector<std::string_view>{"name", "value", "var"});

my_struct mt{2, true, {42}};
t->set_field_value("value", mt);
t->set_field_value("name", std::string("test"));
t->set_field_value("var", 41);
nest1 *st = dynamic_cast<nest1 *>(t.get());
auto p = *st;
std::cout << p.name << "\n";
auto &r0 = t->get_field_value<std::string>("name");
CHECK(r0 == "test");
auto &r = t->get_field_value<int>("var");
CHECK(r == 41);
auto &r1 = t->get_field_value<my_struct>("value");
CHECK(r1.x == 2);
```
“根据对象实例和字段名获取或设置字段的值” 如果字段名不存在则会抛异常;如果设置的值类型和结构体字段类型不相同则会抛异常;需要类型完全一样,不允许隐式转换。比如字段类型是double,但是设置字段的值类型是int也会抛异常,必须显式传double;如果字段类型是std::string, 设置值类型是const char * 同样会报错;如果字段类型是int32_t, 设置值类型是uint_8也会抛异常,因为类型不相同。
设置字段值时也可以显式指定字段类型:
```cpp
t->set_field_value<std::string>("name", "test");
```
这种方式则不要求设置值的类型和字段类型完全一样,只要能赋值成功即可。如果显式指定的字段类型不是实际的字段类型时也会抛异常。

## benchmark
在benchmark monster场景下,struct_pb 性能比protobuf 更好,序列化速度是protobuf的2.4倍,反序列化是protobuf的3.4倍。详情可以自行运行struct_pack 中的benchmark复现结果。

Expand Down Expand Up @@ -96,10 +158,10 @@ oneof -> `std::variant <...>`
- 目前还只支持proto3,不支持proto2;
- 目前还没支持反射;
- 还没支持unkonwn字段;
- struct_pb 结构体必须派生于pb_base_impl
- struct_pb 结构体必须派生于base_impl

## roadmap
- 支持proto2;
- 支持反射;
- 支持unkonwn字段;
- 去除struct_pb 结构体必须派生于pb_base_impl的约束
- 去除struct_pb 结构体必须派生于base_impl的约束
2 changes: 1 addition & 1 deletion test/proto/unittest_proto3.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#endif

#define PB_CHECK assert
#define PUBLIC(T) : public iguana::pb_base_impl<T>
#define PUBLIC(T) : public iguana::base_impl<T>

// define the struct as msg in proto
namespace stpb {
Expand Down
Loading

0 comments on commit 90e9f4a

Please sign in to comment.