diff --git a/tool/CMakeLists.txt b/tool/CMakeLists.txt new file mode 100644 index 00000000..f8eec172 --- /dev/null +++ b/tool/CMakeLists.txt @@ -0,0 +1,14 @@ +project(proto_to_struct) + +cmake_minimum_required(VERSION 3.10) + +set(CMAKE_CXX_STANDARD 20) + +find_package(Protobuf REQUIRED) +include_directories(${PROTOBUF_INCLUDE_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +set(SOURCE_FILE proto_to_struct.cpp) + +add_executable(proto_to_struct ${SOURCE_FILE}) +target_link_libraries(proto_to_struct protobuf::libprotobuf protobuf::libprotoc pthread) diff --git a/tool/README.md b/tool/README.md new file mode 100644 index 00000000..b14f72fe --- /dev/null +++ b/tool/README.md @@ -0,0 +1,402 @@ +# Instructions for using the .proto file to struc_pack header tool + +## compile + +libprotobuf and libprotoc version is 3.21.0. + +```shell +mkdir build +cd build +cmake .. && make +``` + +## usage + +Usage: + +```shell +protoc --plugin=protoc-gen-custom=./build/proto_to_struct data.proto --custom_out=:./protos +``` + +data.proto is the original file that is intended to be the structure pack file. + +`--custom_out=` is followed by the path to the generated file. + +data.proto: + +```proto +syntax = "proto3"; + +package mygame; + +option optimize_for = SPEED; +option cc_enable_arenas = true; + +message Vec3 { + float x = 1; + float y = 2; + float z = 3; +} + +message Weapon { + string name = 1; + int32 damage = 2; +} + +message Monster { + Vec3 pos = 1; + int32 mana = 2; + int32 hp = 3; + string name = 4; + bytes inventory = 5; + enum Color { + Red = 0; + Green = 1; + Blue = 2; + } + Color color = 6; + repeated Weapon weapons = 7; + Weapon equipped = 8; + repeated Vec3 path = 9; +} + +message Monsters { + repeated Monster monsters = 1; +} + +message person { + int32 id = 1; + string name = 2; + int32 age = 3; + double salary = 4; +} + +message persons { + repeated person person_list = 1; +} + +message bench_int32 { + int32 a = 1; + int32 b = 2; + int32 c = 3; + int32 d = 4; +} +``` + +generate struct pack file: + +```cpp +#pragma once +#include + +enum class Color { + Red = 0, + Green = 1, + Blue = 2, +}; + +struct Vec3 { + float x; + float y; + float z; +}; +YLT_REFL(Vec3, x, y, z); + +struct Weapon { + std::string name; + int32_t damage; +}; +YLT_REFL(Weapon, name, damage); + +struct Monster { + Vec3 pos; + int32_t mana; + int32_t hp; + std::string name; + std::string inventory; + Color color; + std::vectorweapons; + Weapon equipped; + std::vectorpath; +}; +YLT_REFL(Monster, pos, mana, hp, name, inventory, color, weapons, equipped, path); + +struct Monsters { + std::vectormonsters; +}; +YLT_REFL(Monsters, monsters); + +struct person { + int32_t id; + std::string name; + int32_t age; + double salary; +}; +YLT_REFL(person, id, name, age, salary); + +struct persons { + std::vectorperson_list; +}; +YLT_REFL(persons, person_list); + +struct bench_int32 { + int32_t a; + int32_t b; + int32_t c; + int32_t d; +}; +YLT_REFL(bench_int32, a, b, c, d); + + +``` + +There are two parameters: + +## add_optional + +Generate C++ files in optional struct pack format. + +```shell +protoc --plugin=protoc-gen-custom=./build/proto_to_struct data.proto --custom_out=add_optional:./protos +``` + +In the generated file, `std::string` will be converted to `std::optional`, and the 'class' type(For example `class Foo`) will be converted to `std::optional`. + +```cpp +#pragma once +#include + +enum class Color { + Red = 0, + Green = 1, + Blue = 2, +}; + +struct Vec3 { + float x; + float y; + float z; +}; +YLT_REFL(Vec3, x, y, z); + +struct Weapon { + std::optional name; + int32_t damage; +}; +YLT_REFL(Weapon, name, damage); + +struct Monster { + std::optional pos; + int32_t mana; + int32_t hp; + std::optional name; + std::optional inventory; + Color color; + std::optional> weapons; + std::optional equipped; + std::optional> path; +}; +YLT_REFL(Monster, pos, mana, hp, name, inventory, color, weapons, equipped, path); + +struct Monsters { + std::optional> monsters; +}; +YLT_REFL(Monsters, monsters); + +struct person { + int32_t id; + std::optional name; + int32_t age; + double salary; +}; +YLT_REFL(person, id, name, age, salary); + +struct persons { + std::optional> person_list; +}; +YLT_REFL(persons, person_list); + +struct bench_int32 { + int32_t a; + int32_t b; + int32_t c; + int32_t d; +}; +YLT_REFL(bench_int32, a, b, c, d); + + +``` + +## enable_inherit + +Generate C++ files in non std::optional format and the file conforms to the `struct pb` standard. + +```shell +protoc --plugin=protoc-gen-custom=./build/proto_to_struct data.proto --custom_out=enable_inherit:./protos +``` + +```cpp +#pragma once +#include + +enum class Color { + Red = 0, + Green = 1, + Blue = 2, +}; + +struct Vec3 : public iguana::base_impl { + Vec3() = default; + Vec3(float a, float b, float c) : x(a), y(b), z(c) {} + float x; + float y; + float z; +}; +YLT_REFL(Vec3, x, y, z); + +struct Weapon : public iguana::base_impl { + Weapon() = default; + Weapon(std::string a, int32_t b) : name(std::move(a)), damage(b) {} + std::string name; + int32_t damage; +}; +YLT_REFL(Weapon, name, damage); + +struct Monster : public iguana::base_impl { + Monster() = default; + Monster(Vec3 a, int32_t b, int32_t c, std::string d, std::string e, Color f, std::vector g, Weapon h, std::vector i) : pos(a), mana(b), hp(c), name(std::move(d)), inventory(std::move(e)), color(f), weapons(std::move(g)), equipped(h), path(std::move(i)) {} + Vec3 pos; + int32_t mana; + int32_t hp; + std::string name; + std::string inventory; + Color color; + std::vector weapons; + Weapon equipped; + std::vector path; +}; +YLT_REFL(Monster, pos, mana, hp, name, inventory, color, weapons, equipped, path); + +struct Monsters : public iguana::base_impl { + Monsters() = default; + Monsters(std::vector a) : monsters(std::move(a)) {} + std::vector monsters; +}; +YLT_REFL(Monsters, monsters); + +struct person : public iguana::base_impl { + person() = default; + person(int32_t a, std::string b, int32_t c, double d) : id(a), name(std::move(b)), age(c), salary(d) {} + int32_t id; + std::string name; + int32_t age; + double salary; +}; +YLT_REFL(person, id, name, age, salary); + +struct persons : public iguana::base_impl { + persons() = default; + persons(std::vector a) : person_list(std::move(a)) {} + std::vector person_list; +}; +YLT_REFL(persons, person_list); + +struct bench_int32 : public iguana::base_impl { + bench_int32() = default; + bench_int32(int32_t a, int32_t b, int32_t c, int32_t d) : a(a), b(b), c(c), d(d) {} + int32_t a; + int32_t b; + int32_t c; + int32_t d; +}; +YLT_REFL(bench_int32, a, b, c, d); + + +``` + +## add_optional and enable_inherit + +The presence of these two parameters indicates that these two functions take effect on the generated file at the same time. + +```shell +protoc --plugin=protoc-gen-custom=./build/proto_to_struct data.proto --custom_out=add_optional+enable_inherit:./protos +``` + +```cpp +#pragma once +#include + +enum class Color { + Red = 0, + Green = 1, + Blue = 2, +}; + +struct Vec3 : public iguana::base_impl { + Vec3() = default; + Vec3(float a, float b, float c) : x(a), y(b), z(c) {} + float x; + float y; + float z; +}; +YLT_REFL(Vec3, x, y, z); + +struct Weapon : public iguana::base_impl { + Weapon() = default; + Weapon(std::optional a, int32_t b) : name(std::move(a)), damage(b) {} + std::optional name; + int32_t damage; +}; +YLT_REFL(Weapon, name, damage); + +struct Monster : public iguana::base_impl { + Monster() = default; + Monster(std::optional a, int32_t b, int32_t c, std::optional d, std::optional e, Color f, std::optional> g, std::optional h, std::optional> i) : pos(a), mana(b), hp(c), name(std::move(d)), inventory(std::move(e)), color(f), weapons(std::move(g)), equipped(h), path(std::move(i)) {} + std::optional pos; + int32_t mana; + int32_t hp; + std::optional name; + std::optional inventory; + Color color; + std::optional> weapons; + std::optional equipped; + std::optional> path; +}; +YLT_REFL(Monster, pos, mana, hp, name, inventory, color, weapons, equipped, path); + +struct Monsters : public iguana::base_impl { + Monsters() = default; + Monsters(std::optional> a) : monsters(std::move(a)) {} + std::optional> monsters; +}; +YLT_REFL(Monsters, monsters); + +struct person : public iguana::base_impl { + person() = default; + person(int32_t a, std::optional b, int32_t c, double d) : id(a), name(std::move(b)), age(c), salary(d) {} + int32_t id; + std::optional name; + int32_t age; + double salary; +}; +YLT_REFL(person, id, name, age, salary); + +struct persons : public iguana::base_impl { + persons() = default; + persons(std::optional> a) : person_list(std::move(a)) {} + std::optional> person_list; +}; +YLT_REFL(persons, person_list); + +struct bench_int32 : public iguana::base_impl { + bench_int32() = default; + bench_int32(int32_t a, int32_t b, int32_t c, int32_t d) : a(a), b(b), c(c), d(d) {} + int32_t a; + int32_t b; + int32_t c; + int32_t d; +}; +YLT_REFL(bench_int32, a, b, c, d); + + +``` \ No newline at end of file diff --git a/tool/proto_to_struct.cpp b/tool/proto_to_struct.cpp new file mode 100644 index 00000000..d4028d2c --- /dev/null +++ b/tool/proto_to_struct.cpp @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "struct_code_generator.hpp" +#include "struct_token.hpp" + +bool write_to_output(google::protobuf::io::ZeroCopyOutputStream* output, + const void* data, int size) { + const uint8_t* in = reinterpret_cast(data); + int in_size = size; + + void* out; + int out_size; + + while (true) { + if (!output->Next(&out, &out_size)) { + return false; + } + + if (in_size <= out_size) { + memcpy(out, in, in_size); + output->BackUp(out_size - in_size); + return true; + } + + memcpy(out, in, out_size); + in += out_size; + in_size -= out_size; + } +} + +class struct_code_generator : public google::protobuf::compiler::CodeGenerator { + public: + virtual ~struct_code_generator() {} + + virtual bool Generate(const google::protobuf::FileDescriptor* file, + const std::string& parameter, + google::protobuf::compiler::GeneratorContext* context, + std::string* error) const override { + std::string filename = file->name() + ".h"; + + auto output = context->Open(filename); + + bool enable_optional = false; + bool enable_inherit = false; + + if (parameter.find("add_optional") != std::string::npos) { + enable_optional = true; + } + + if (parameter.find("enable_inherit") != std::string::npos) { + enable_inherit = true; + } + + // Use ZeroCopyOutputStream + google::protobuf::io::ZeroCopyOutputStream* zero_copy_output = output; + + std::vector proto_module_info; + std::vector proto_enum_info; + for (int i = 0; i < file->message_type_count(); ++i) { + // struct name + const google::protobuf::Descriptor* descriptor = file->message_type(i); + + struct_enum enum_token; + enum_token.clear(); + enum_token.get_enum_fields(descriptor); + proto_enum_info.emplace_back(enum_token); + + struct_tokenizer tokenizer; + tokenizer.clear(); + tokenizer.tokenizer(descriptor); + proto_module_info.emplace_back(tokenizer); + } + + std::string struct_header = code_generate_header(); + write_to_output(zero_copy_output, (const void*)struct_header.c_str(), + struct_header.size()); + + // codegen struct enum + for (auto enum_inst : proto_enum_info) { + std::string enum_str = ""; + enum_str = code_generate_enum(enum_inst); + write_to_output(zero_copy_output, (const void*)enum_str.c_str(), + enum_str.size()); + } + + // codegen struct + std::vector struct_module_contents; + + for (auto single_struct : proto_module_info) { + std::string struct_default_str = ""; + std::string struct_constructor_str = ""; + std::string struct_body_str = ""; + std::string struct_macro_str = ""; + + struct_default_str = code_generate_struct_default( + single_struct.get_struct_name(), enable_inherit); + struct_constructor_str = code_generate_struct_constructor( + single_struct.get_struct_name(), single_struct.get_tokens(), + enable_optional); + struct_body_str = + code_generate_body(single_struct.get_tokens(), enable_optional); + struct_macro_str = code_generate_ylt_macro( + single_struct.get_struct_name(), single_struct.get_tokens()); + + write_to_output(zero_copy_output, (const void*)struct_default_str.c_str(), + struct_default_str.size()); + if (enable_inherit) + write_to_output(zero_copy_output, + (const void*)struct_constructor_str.c_str(), + struct_constructor_str.size()); + write_to_output(zero_copy_output, (const void*)struct_body_str.c_str(), + struct_body_str.size()); + write_to_output(zero_copy_output, (const void*)struct_macro_str.c_str(), + struct_macro_str.size()); + } + + delete zero_copy_output; + return true; + } +}; + +int main(int argc, char* argv[]) { + google::protobuf::compiler::PluginMain(argc, argv, + new struct_code_generator()); + return 0; +} \ No newline at end of file diff --git a/tool/struct_code_generator.hpp b/tool/struct_code_generator.hpp new file mode 100644 index 00000000..7648850b --- /dev/null +++ b/tool/struct_code_generator.hpp @@ -0,0 +1,266 @@ +#pragma once +#include + +#include "struct_token.hpp" + +char parameter_value[27] = "abcdefghijklmnopqrstuvwxyz"; + +std::string code_generate_header() { + std::string result = "#pragma once\n#include \n\n"; + return result; +} + +std::string code_generate_struct_default(const std::string &struct_name, + bool enable_inherit) { + std::string result = "struct "; + result.append(struct_name); + + if (enable_inherit) { + result.append(" : public iguana::base_impl<"); + result.append(struct_name); + result.append("> "); + } + + result.append(" {\n"); + + if (enable_inherit) { + result.append("\t"); + result.append(struct_name); + result.append("() = default;\n\t"); + } + return result; +} + +std::string code_generate_struct_constructor( + const std::string &struct_name, const std::vector lists, + bool add_optional) { + std::string result = struct_name + "("; + int i = 0; + int list_size = lists.size() - 1; + for (auto it = lists.begin(); it != lists.end(); it++) { + if (it->type == struct_token_type::pod) { + if (it->lable == lable_type::lable_repeated) { + result.append("std::vector<"); + result.append(it->type_name); + result.append("> "); + result += parameter_value[i]; + } + else { + result.append(it->type_name); + result.append(" "); + result += parameter_value[i]; + } + if (i != list_size) + result.append(", "); + i++; + } + else if (it->type == struct_token_type::proto_string) { + if (it->lable == lable_type::lable_repeated) { + if (add_optional) { + result.append("std::optional<"); + } + result.append("std::vector"); + if (add_optional) { + result.append("> "); + } + result += parameter_value[i]; + } + else { + if (add_optional) { + result.append("std::optional<"); + } + + result.append("std::"); + result.append(it->type_name); + if (add_optional) { + result.append(">"); + } + result.append(" "); + result += parameter_value[i]; + } + if (i != list_size) + result.append(", "); + i++; + } + else if (it->type == struct_token_type::enum_type) { + // enum no repeated type + result.append(it->type_name); + result.append(" "); + result += parameter_value[i]; + if (i != list_size) + result.append(", "); + i++; + } + else { + if (it->lable == lable_type::lable_repeated) { + if (add_optional) { + result.append("std::optional<"); + } + + result.append("std::vector<"); + + result.append(it->type_name); + result.append(">"); + + if (add_optional) { + result.append("> "); + } + + result += parameter_value[i]; + } + else { + if (add_optional) { + result.append("std::optional<"); + } + result.append(it->type_name); + if (add_optional) { + result.append(">"); + } + result.append(" "); + result += parameter_value[i]; + } + if (i != list_size) + result.append(", "); + i++; + } + } + result.append(") : "); + + int j = 0; + for (auto ll : lists) { + if (ll.type == struct_token_type::pod || + ll.type == struct_token_type::message || + ll.type == struct_token_type::enum_type) { + result.append(ll.var_name); + result.append("("); + if (ll.lable == lable_type::lable_repeated) { + result.append("std::move("); + result += parameter_value[j]; + result.append(")"); + if (j != list_size) + result.append("), "); + else + result.append(") {}"); + } + else { + result += parameter_value[j]; + if (j != list_size) + result.append("), "); + else + result.append(") {}"); + } + } + else if (ll.type == struct_token_type::proto_string) { + result.append(ll.var_name); + result.append("("); + result.append("std::move("); + result += parameter_value[j]; + result.append(")"); + if (j != list_size) + result.append("), "); + else + result.append(") {}"); + } + j++; + } + result.append("\n"); + return result; +} + +std::string code_generate_body(const std::vector &lists, + bool add_optional) { + std::string result; + for (auto ll : lists) { + result.append("\t"); + if (ll.lable == lable_type::lable_repeated) { + if (ll.type == struct_token_type::proto_string) { + if (add_optional) { + result.append("std::optional<"); + } + + result.append("std::vector"); + if (add_optional) { + result.append("> "); + } + result.append(ll.var_name); + result.append(";\n"); + } + else { + if (ll.type == struct_token_type::message && add_optional) { + result.append("std::optional<"); + } + result.append("std::vector<"); + result.append(ll.type_name); + result.append(">"); + if (ll.type == struct_token_type::message && add_optional) { + result.append("> "); + } + result.append(ll.var_name); + result.append(";\n"); + } + } + else { + if (ll.type != struct_token_type::pod && + ll.type != struct_token_type::enum_type && add_optional) { + result.append("std::optional<"); + } + + if (ll.type == struct_token_type::proto_string) + result.append("std::"); + result.append(ll.type_name); + if (ll.type != struct_token_type::pod && + ll.type != struct_token_type::enum_type && add_optional) { + result.append(">"); + } + result.append(" "); + result.append(ll.var_name); + result.append(";\n"); + } + } + result.append("};\n"); + + return result; +} + +std::string code_generate_ylt_macro(const std::string &struct_name, + const std::vector &lists) { + std::string result = "YLT_REFL("; + result.append(struct_name); + result.append(", "); + int i = 0; + int list_size = lists.size() - 1; + for (auto ll : lists) { + if (i != list_size) { + result.append(ll.var_name); + result.append(", "); + } + else { + result.append(ll.var_name); + } + i++; + } + result.append(");\n\n"); + return result; +} + +std::string code_generate_enum(const struct_enum &enum_inst) { + std::string result = "enum class "; + if (enum_inst.enum_name_.empty()) + return ""; + result.append(enum_inst.enum_name_); + result.append(" {\n"); + if (enum_inst.fields_.size() == 0) { + result.append("}\n"); + } + else { + for (auto i : enum_inst.fields_) { + result.append("\t"); + result.append(i.first); + result.append(" = "); + result.append(std::to_string(i.second)); + result.append(",\n"); + } + result.append("};\n\n"); + } + return result; +} \ No newline at end of file diff --git a/tool/struct_token.hpp b/tool/struct_token.hpp new file mode 100644 index 00000000..61bfe4f4 --- /dev/null +++ b/tool/struct_token.hpp @@ -0,0 +1,125 @@ +#pragma once +#include + +#include +#include + +enum class struct_token_type { + pod, + proto_string, + message, + enum_type, + null_type +}; + +enum class lable_type { + lable_optional, + lable_required, + lable_repeated, + lable_null +}; + +struct struct_token { + struct_token() { + this->var_name = ""; + this->type_name = ""; + this->type = struct_token_type::null_type; + this->lable = lable_type::lable_null; + } + + void clear(); + std::string var_name; + std::string type_name; + struct_token_type type; + lable_type lable; +}; + +class struct_tokenizer { + public: + struct_tokenizer() { clear(); } + + void tokenizer(const google::protobuf::Descriptor* descriptor) { + struct_name_ = descriptor->name(); + for (int j = 0; j < descriptor->field_count(); ++j) { + struct_token token = {}; + const google::protobuf::FieldDescriptor* field = descriptor->field(j); + token.var_name = field->name(); + if (field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) { + token.type_name = field->message_type()->name(); + token.type = struct_token_type::message; + } + else if (field->type() == + google::protobuf::FieldDescriptor::TYPE_STRING) { + token.type_name = field->type_name(); + token.type = struct_token_type::proto_string; + // std::cout << "string var anme is: " << token.var_name << std::endl; + } + else { + token.type_name = field->type_name(); + if (token.type_name == "bytes") { + token.type = struct_token_type::proto_string; + token.type_name = "string"; + } + else if (token.type_name == "enum") { + const google::protobuf::EnumDescriptor* enum_desc = + field->enum_type(); + token.type_name = enum_desc->name(); + token.type = struct_token_type::enum_type; + } + else { + token.type = struct_token_type::pod; + if (token.type_name.find("int") != std::string::npos) + token.type_name += "_t"; + } + } + + if (field->label() == + google::protobuf::FieldDescriptor::Label::LABEL_REPEATED) + token.lable = lable_type::lable_repeated; + else if (field->label() == + google::protobuf::FieldDescriptor::Label::LABEL_OPTIONAL) + token.lable = lable_type::lable_optional; + else if (field->label() == + google::protobuf::FieldDescriptor::Label::LABEL_REQUIRED) + token.lable = lable_type::lable_required; + + token_lists_.emplace_back(token); + } + } + + void clear() { + token_lists_.clear(); + struct_name_ = ""; + } + + std::vector& get_tokens() { return token_lists_; } + std::string& get_struct_name() { return struct_name_; } + + private: + std::vector token_lists_; + std::string struct_name_; // struct name +}; + +struct struct_enum { + struct_enum() { clear(); } + + void clear() { + this->enum_name_ = ""; + this->fields_.clear(); + } + + void get_enum_fields(const google::protobuf::Descriptor* descriptor) { + for (int e = 0; e < descriptor->enum_type_count(); ++e) { + const google::protobuf::EnumDescriptor* enum_desc = + descriptor->enum_type(e); + enum_name_ = enum_desc->name(); + for (int v = 0; v < enum_desc->value_count(); ++v) { + const google::protobuf::EnumValueDescriptor* value_desc = + enum_desc->value(v); + fields_.push_back({value_desc->name(), value_desc->number()}); + } + } + } + std::string enum_name_; + std::vector> fields_; +}; \ No newline at end of file