feat: experiment map based type erasure.

This commit is contained in:
insleker 2024-04-23 15:44:09 +08:00
parent e0269954ca
commit da420c625f
15 changed files with 509 additions and 0 deletions

24
.clang-format Normal file
View File

@ -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

3
.gitignore vendored
View File

@ -30,3 +30,6 @@
*.exe *.exe
*.out *.out
*.app *.app
build/
*Presets.json

20
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -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
}

15
.vscode/extensions.json vendored Normal file
View File

@ -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"
]
}

15
.vscode/settings.json vendored Normal file
View File

@ -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
},
}

14
CMakeLists.txt Normal file
View File

@ -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)

View File

@ -1,2 +1,6 @@
# event_transformer # event_transformer
Simple utility lib to be interface of event system and simplify transform between different evet lib. Simple utility lib to be interface of event system and simplify transform between different evet lib.
## ref
- [MCGallaspy/events](https://github.com/MCGallaspy/events/)

70
conanfile.py Normal file
View File

@ -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 = "<Put the package license here>"
author = "<Put your name here> <And your email here>"
url = "<Package recipe repository url here, for issues about the package>"
description = "<Description of event_transformer package here>"
topics = ("<Put some tag here>", "<here>", "<and here>")
# 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"]

View File

@ -0,0 +1,34 @@
#pragma once
using namespace std;
template<class... EventTypes>
class EventList;
template<>
class EventList<>
{};
/**
* @todo addition type but without trigger ambiguous error.
*/
template<class Event>
class EventList<Event> : 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 Event, class... Others>
class EventList<Event, Others...>
: public EventList<Event>
, public EventList<Others...>
{};

View File

@ -0,0 +1,3 @@
#include <iostream>
#include "event_transformer.h"

View File

@ -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)

26
test_package/conanfile.py Normal file
View File

@ -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")

View File

@ -0,0 +1,7 @@
#include "event_transformer.h"
#include <vector>
#include <string>
int main() {
}

10
tests/CMakeLists.txt Normal file
View File

@ -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)

255
tests/test_event_basic.cpp Normal file
View File

@ -0,0 +1,255 @@
#include <iostream>
#include <memory>
#include <stdio.h>
#include <type_traits>
#include <typeinfo>
#include <vector>
// #include <source_location>
// #include <string_view>
#include <chrono>
#include <event_transformer.h>
#include <gtest/gtest.h>
#include <so_5/all.hpp>
#include <string>
#include <typeinfo>
#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<AEvent, BEvent>
{
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<typename EVT>
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<EventList<decltype(evt)>&>(*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<EventList<AEvent>>(foo).dispatch(a_ev);
// static_cast<Foo>(foo).dispatch(a_ev);
static_cast<EventList<AEvent>&>(foo).dispatch(a_ev);
static_cast<EventList<BEvent>&>(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<EventList<AEvent>&>(foo).dispatch(a_ev);
foo_pp.dispatch(b_ev);
};
/********************************************************************************************************/
#include <any>
#include <map>
#include <typeindex>
#include <typeinfo>
/**
* @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<AEvent, BEvent>
{
// struct Concept
// { // (5)
// virtual ~Concept()
// {
// }
// virtual void dispatch() = 0;
// };
// std::unique_ptr<Concept> pimpl_;
// template<typename T> // (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<typename EVT>
// // void dispatch(EVT evt)
// // {
// // // I want to override this. (type erase)
// // std::cout << "base interface\n";
// // };
template<typename EVT>
void dispatch(EVT evt)
{
using namespace std;
// for (auto const& [key, val] : fmap) {
// }
auto func = std::any_cast<std::function<void(EVT&)>>(fmap.at(type_index(typeid(EVT))));
func(evt); // dispatch
};
protected:
// void (*)(std::any &);
std::map<std::type_index, std::any> fmap;
}; // Fake_module_process_ev
/**
* friend `dispatch(foo, a_event);` `foo.dispatch(a_event);`
*/
/**
* I wan't this be implement.
*
*/
// template<typename Functor>
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<void(AEvent&)> func = [this](AEvent& evt) -> void { so_5::send<AEvent>(target); };
fmap[type_index(typeid(AEvent))] = std::any(func);
// Functor<>;
}
// template<typename EVT>
// 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<EventList<decltype(evt)>&>(*this).dispatch(evt);
// so_5::send<EVT>(target);
// }
};
/**
* simulate module process which require event interface
*/
void
fake_module_process(Fake_module_process_ev* evt_li_ptr)
// fake_module_process(EventList<AEvent, BEvent>* 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<EventList<decltype(a_ev)>&>(*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<AEvent> 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<pinger>();
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
}
}