From da420c625f0f1bf11a92098a58097bb79439c710 Mon Sep 17 00:00:00 2001 From: insleker Date: Tue, 23 Apr 2024 15:44:09 +0800 Subject: [PATCH] feat: experiment map based type erasure. --- .clang-format | 24 ++++ .gitignore | 3 + .vscode/c_cpp_properties.json | 20 +++ .vscode/extensions.json | 15 ++ .vscode/settings.json | 15 ++ CMakeLists.txt | 14 ++ README.md | 4 + conanfile.py | 70 ++++++++++ include/event_transformer.h | 34 +++++ src/event_transformer.cpp | 3 + test_package/CMakeLists.txt | 9 ++ test_package/conanfile.py | 26 ++++ test_package/src/example.cpp | 7 + tests/CMakeLists.txt | 10 ++ tests/test_event_basic.cpp | 255 ++++++++++++++++++++++++++++++++++ 15 files changed, 509 insertions(+) create mode 100644 .clang-format create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 CMakeLists.txt create mode 100644 conanfile.py create mode 100644 include/event_transformer.h create mode 100644 src/event_transformer.cpp create mode 100644 test_package/CMakeLists.txt create mode 100644 test_package/conanfile.py create mode 100644 test_package/src/example.cpp create mode 100644 tests/CMakeLists.txt create mode 100644 tests/test_event_basic.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..176a88f --- /dev/null +++ b/.clang-format @@ -0,0 +1,24 @@ +--- +Language: Cpp +BasedOnStyle: Mozilla +AlignAfterOpenBracket: Align +AllowShortFunctionsOnASingleLine: None +#AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +ArrayInitializerAlignmentStyle: Left +ColumnLimit: 120 +IndentWidth: 2 +# TabWidth: 4 +UseTab: Never +# IndentCaseLabels: false +MaxEmptyLinesToKeep: 2 +BreakBeforeBraces: Mozilla +# BreakBeforeBraces: Allman +# SpaceBeforeParens: ControlStatements +# SpaceBeforeAssignmentOperators: true +# SpacesInSquareBrackets: false +# SpaceBeforeSquareBrackets: false +# SpaceAfterAssignmentOperators: true +# SpaceBeforeCpp11BracedList: false +# ContinuationIndentWidth: 4 +UseCRLF: false diff --git a/.gitignore b/.gitignore index 259148f..5485546 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ *.exe *.out *.app + +build/ +*Presets.json diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..a5c1e01 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,20 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "~/.conan2/p/caf*/s/src/**", + "~/.conan2/p/sobje*/s/src/dev/**", + "~/.conan2/p/spdlo*/s/src/**", + "~/.conan2/p/gtest*/p/include/**" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cppStandard": "gnu++17", + "intelliSenseMode": "linux-gcc-x64", + "cStandard": "c23" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..587d362 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,15 @@ +{ + "recommendations": [ + "ms-vscode.cmake-tools", + "ms-vscode.cpptools-extension-pack", + "mhutchie.git-graph", + "ms-vscode-remote.remote-ssh", + "ms-python.python", + "ms-python.vscode-pylance", + "bierner.markdown-mermaid", + "yzhang.markdown-all-in-one", + "hediet.vscode-drawio", + "cheshirekow.cmake-format", + "charliermarsh.ruff" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8ba14f8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "files.associations": { + "*.h": "cpp", + "*.hpp": "cpp", + "*.cc": "cpp", + "*.cpp": "cpp", + }, + "C_Cpp.autoAddFileAssociations": false, + "files.exclude": { + "**/build": true + }, + "search.exclude": { + "**/build": true + }, +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6d9c0e8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.15) +project(event_transformer CXX) + +add_library(event_transformer INTERFACE) +target_include_directories(event_transformer INTERFACE include) + +if(NOT BUILD_TESTING STREQUAL OFF) + enable_testing() + add_subdirectory(tests) +endif() + +set_target_properties(event_transformer + PROPERTIES PUBLIC_HEADER "include/event_transformer.h") +install(TARGETS event_transformer) diff --git a/README.md b/README.md index 2b4abe1..9aeb92d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # event_transformer Simple utility lib to be interface of event system and simplify transform between different evet lib. + +## ref + +- [MCGallaspy/events](https://github.com/MCGallaspy/events/) \ No newline at end of file diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..f27eeac --- /dev/null +++ b/conanfile.py @@ -0,0 +1,70 @@ +from conan import ConanFile +from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps +from conan.tools.build import check_min_cppstd, can_run + + +class event_transformerRecipe(ConanFile): + name = "event_transformer" + version = "0.1" + package_type = "library" + + # Optional metadata + license = "" + author = " " + url = "" + description = "" + topics = ("", "", "") + + # Binary configuration + settings = "os", "compiler", "build_type", "arch" + options = {"shared": [True, False], "fPIC": [True, False]} + default_options = {"shared": False, "fPIC": True} + + # Sources are located in the same place as this recipe, copy them to the recipe + exports_sources = "CMakeLists.txt", "src/*", "include/*" + + def requirements(self): + """ + todo clean up this thing. + """ + # build + self.tool_requires("cmake/[>=3.14 <=3.26]") + # test + self.test_requires("gtest/[~1.13]") + self.test_requires("sobjectizer/[~5.8]") + self.test_requires("eventpp/[~0.1]") + self.test_requires("sigslot/[~1.2]") + self.test_requires("caf/[~0.19]") + self.test_requires("spdlog/[~1.13]") + + def validate(self): + check_min_cppstd(self, "17") + + def config_options(self): + if self.settings.os == "Windows": + self.options.rm_safe("fPIC") + + def configure(self): + if self.options.shared: + self.options.rm_safe("fPIC") + + def layout(self): + cmake_layout(self) + + def generate(self): + deps = CMakeDeps(self) + deps.generate() + tc = CMakeToolchain(self) + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.libs = ["event_transformer"] diff --git a/include/event_transformer.h b/include/event_transformer.h new file mode 100644 index 0000000..88c8b46 --- /dev/null +++ b/include/event_transformer.h @@ -0,0 +1,34 @@ +#pragma once + +using namespace std; + +template +class EventList; + +template<> +class EventList<> +{}; + +/** + * @todo addition type but without trigger ambiguous error. + */ +template +class EventList : public virtual EventList<> +{ +public: + virtual void dispatch(const Event&){}; //=0 + // virtual void dispatch(Event&) + // { + // cout << "default cb\n"; + // }; + // virtual void dispatch(const Event) + // { + // cout << "default cb\n"; + // }; +}; + +template +class EventList + : public EventList + , public EventList +{}; \ No newline at end of file diff --git a/src/event_transformer.cpp b/src/event_transformer.cpp new file mode 100644 index 0000000..19b32c8 --- /dev/null +++ b/src/event_transformer.cpp @@ -0,0 +1,3 @@ +#include +#include "event_transformer.h" + diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt new file mode 100644 index 0000000..f9cfaa0 --- /dev/null +++ b/test_package/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.15) +project(PackageTest CXX) + +find_package(event_transformer CONFIG REQUIRED) + + + +add_executable(example src/example.cpp) +target_link_libraries(example event_transformer::event_transformer) diff --git a/test_package/conanfile.py b/test_package/conanfile.py new file mode 100644 index 0000000..a5c14cf --- /dev/null +++ b/test_package/conanfile.py @@ -0,0 +1,26 @@ +import os + +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout +from conan.tools.build import can_run + + +class event_transformerTestConan(ConanFile): + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeDeps", "CMakeToolchain" + + def requirements(self): + self.requires(self.tested_reference_str) + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def layout(self): + cmake_layout(self) + + def test(self): + if can_run(self): + cmd = os.path.join(self.cpp.build.bindir, "example") + self.run(cmd, env="conanrun") diff --git a/test_package/src/example.cpp b/test_package/src/example.cpp new file mode 100644 index 0000000..2632f86 --- /dev/null +++ b/test_package/src/example.cpp @@ -0,0 +1,7 @@ +#include "event_transformer.h" +#include +#include + +int main() { + +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..22200c1 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.14) +include(GoogleTest) +find_package(GTest REQUIRED) +include_directories(${GTEST_INCLUDE_DIRS}) +find_package(sobjectizer REQUIRED CONFIG) + +add_executable(evt_trans_tests test_event_basic.cpp) +target_link_libraries(evt_trans_tests event_transformer GTest::gtest_main + GTest::gmock_main sobjectizer::StaticLib) +gtest_discover_tests(evt_trans_tests) diff --git a/tests/test_event_basic.cpp b/tests/test_event_basic.cpp new file mode 100644 index 0000000..a21f174 --- /dev/null +++ b/tests/test_event_basic.cpp @@ -0,0 +1,255 @@ +#include +#include +#include +#include +#include +#include +// #include +// #include +#include +#include +#include +#include +#include +#include + +#define TYPE_NAME(x) #x + +namespace { +using namespace std; +using namespace ::testing; + +/** + * original required by `events` lib + */ +class BaseEvent +{}; + +struct AEvent +{ + // const string name = TYPE_NAME(AEvent); //not stable + double speed; // parameter of event +}; +struct BEvent : public BaseEvent +{ + // const string name = TYPE_NAME(BEvent); //not stable +}; +struct DEvent : public AEvent +{}; // Events can be arbitrarily derived. + +class Foo : public EventList +{ +public: + void dispatch(const AEvent& evt) override + { + cout << "Foo is handling AEvent" << endl; + } + void dispatch(const BEvent& evt) override + { + cout << "Foo is handling BEvent" << endl; + } +}; + + +class Foo_pp : public Foo +{ + string actor = "foo"; + +public: + // for c++ 17 + template + void dispatch(EVT evt) + { + // cout << "this is additional wrapper from event name: " << evt.name << "\n"; + cout << "this is additional wrapper from event name: " + << "\n"; + static_cast&>(*this).dispatch(evt); + } +}; + + +class Evt_trans_test : public Test +{ +protected: + const AEvent a_ev = AEvent(); + const BEvent b_ev = BEvent(); +}; +TEST_F(Evt_trans_test, create_test) +{ + Foo foo; + // static_cast>(foo).dispatch(a_ev); + // static_cast(foo).dispatch(a_ev); + static_cast&>(foo).dispatch(a_ev); + static_cast&>(foo).dispatch(b_ev); + // foo.dispatch(a_ev); + foo.dispatch(std::ref(a_ev)); + + printf("\n=======================splitter=======================\n\n"); + Foo_pp foo_pp; + // static_cast&>(foo).dispatch(a_ev); + foo_pp.dispatch(b_ev); +}; + +/********************************************************************************************************/ +#include +#include +#include +#include + +/** + * @details + * I want this be interface only. + * @todo require a dynamic map to map from rtti typeinfo to coresponding function。 + */ +class Fake_module_process_ev : public EventList +{ + // struct Concept + // { // (5) + // virtual ~Concept() + // { + // } + // virtual void dispatch() = 0; + // }; + + // std::unique_ptr pimpl_; + + // template // (6) + // struct Model : Concept + // { + // Model(const T& t) + // : object(t) + // { + // } + // void dispatch() override + // { + // return object.dispatch(); + // } + + // private: + // T object; + // }; + + +public: + // /** + // * @todo type erase + // * @see https://www.hmoonotes.org/2023/06/cpp-type-erasure.html + // * @see https://www.modernescpp.com/index.php/type-erasure/ + // */ + // // template + // // void dispatch(EVT evt) + // // { + // // // I want to override this. (type erase) + // // std::cout << "base interface\n"; + // // }; + template + void dispatch(EVT evt) + { + using namespace std; + // for (auto const& [key, val] : fmap) { + // } + auto func = std::any_cast>(fmap.at(type_index(typeid(EVT)))); + func(evt); // dispatch + }; + +protected: + // void (*)(std::any &); + std::map fmap; +}; // Fake_module_process_ev + +/** + * 用friend 會變成 `dispatch(foo, a_event);` 而非 `foo.dispatch(a_event);` + */ + +/** + * I wan't this be implement. + * 模板派生类 + */ +// template +class So5_fake_module_process_ev : virtual public Fake_module_process_ev +{ + so_5::mbox_t target; + +public: + So5_fake_module_process_ev(so_5::mbox_t target_) + : target(target_) + { + using namespace std; + // iterate over all the inherit class and generate function from functor. + // + std::function func = [this](AEvent& evt) -> void { so_5::send(target); }; + fmap[type_index(typeid(AEvent))] = std::any(func); + // Functor<>; + } + // template + // void dispatch(EVT evt) + // { + // // cout << "this is additional wrapper from event name: " << evt.name << "\n"; + // cout << "this is additional wrapper from event name: " + // << "\n"; + // // static_cast&>(*this).dispatch(evt); + // so_5::send(target); + // } +}; + +/** + * simulate module process which require event interface + */ +void +fake_module_process(Fake_module_process_ev* evt_li_ptr) +// fake_module_process(EventList* evt_li_ptr) +{ + std::cout << "fake module called\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + const AEvent a_ev = AEvent{ .speed = 5.0 }; + evt_li_ptr->dispatch(a_ev); + // static_cast&>(*evt_li_ptr).dispatch(a_ev); + std::cout << "fake module exist\n"; + // +} +class pinger final : public so_5::agent_t +{ + so_5::mbox_t ponger_; + + void on_pong(mhood_t cmd) + { + std::cout << "actor pinger: received a A event\n"; + } + +public: + pinger(context_t ctx) + : so_5::agent_t{ std::move(ctx) } + { + } + + void set_ponger(const so_5::mbox_t mbox) + { + ponger_ = mbox; + } + + void so_define_agent() override + { + so_subscribe_self().event(&pinger::on_pong); + } +}; +TEST_F(Evt_trans_test, so5_fake_module_test) +{ + // using namespace std::literals; + using namespace std::chrono_literals; + + so_5::launch([](so_5::environment_t& env) { + so_5::mbox_t m; + env.introduce_coop([&m](so_5::coop_t& coop) { + auto pinger_actor = coop.make_agent(); + m = pinger_actor->so_direct_mbox(); + }); + So5_fake_module_process_ev ev_sys(m); + fake_module_process(&ev_sys); + // pause(200ms); + std::this_thread::sleep_for(200ms); + + std::cout << "Stopping..." << std::endl; + env.stop(); + }); // end of so5 launch +} +} \ No newline at end of file