Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamic reflection #285

Merged
merged 10 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading