working bridge

This commit is contained in:
Green Sky 2023-12-15 22:40:11 +01:00
commit 7a05a87e72
No known key found for this signature in database
8 changed files with 480 additions and 0 deletions

26
.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
.vs/
*.o
*.swp
~*
*~
.idea/
cmake-build-debug/
cmake-build-debugandtest/
cmake-build-release/
*.stackdump
*.coredump
compile_commands.json
/build*
/result*
.clangd
.cache
.DS_Store
.AppleDouble
.LSOverride
CMakeLists.txt.user*
CMakeCache.txt
*.tox
imgui.ini

70
CMakeLists.txt Normal file
View File

@ -0,0 +1,70 @@
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
# cmake setup begin
project(solanaceae_bridge)
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(SOLANACEAE_BRIDGE_STANDALONE ON)
else()
set(SOLANACEAE_BRIDGE_STANDALONE OFF)
endif()
message("II SOLANACEAE_BRIDGE_STANDALONE " ${SOLANACEAE_BRIDGE_STANDALONE})
option(SOLANACEAE_BRIDGE_BUILD_PLUGINS "Build the bridge plugin" ${SOLANACEAE_BRIDGE_STANDALONE})
if (SOLANACEAE_BRIDGE_STANDALONE)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# defaulting to debug mode, if not specified
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()
# setup my vim ycm :D
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# more paths
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
endif()
# external libs
add_subdirectory(./external) # before increasing warn levels, sad :(
if (SOLANACEAE_BRIDGE_STANDALONE)
set(CMAKE_CXX_EXTENSIONS OFF)
# bump up warning levels appropriately for clang, gcc & msvc
if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
add_compile_options(
-Wall -Wextra # Reasonable and standard
-Wpedantic # Warn if non-standard C++ is used
-Wunused # Warn on anything being unused
#-Wconversion # Warn on type conversions that may lose data
#-Wsign-conversion # Warn on sign conversions
-Wshadow # Warn if a variable declaration shadows one from a parent context
)
if (NOT WIN32)
#link_libraries(-fsanitize=address,undefined)
#link_libraries(-fsanitize=undefined)
endif()
elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC")
if (CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
endif()
endif()
endif()
# cmake setup end
add_subdirectory(./src)
if (SOLANACEAE_BRIDGE_BUILD_PLUGINS)
message("II SOLANACEAE_BRIDGE_BUILD_PLUGINS " ${SOLANACEAE_BRIDGE_BUILD_PLUGINS})
add_subdirectory(./plugins)
endif()

50
external/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,50 @@
cmake_minimum_required(VERSION 3.14...3.24 FATAL_ERROR)
include(FetchContent)
# TODO: move entt dep into solanaceae_contact
if (NOT TARGET EnTT::EnTT)
FetchContent_Declare(EnTT
GIT_REPOSITORY https://github.com/skypjack/entt.git
GIT_TAG v3.12.2
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(EnTT)
endif()
if (NOT TARGET solanaceae_util)
FetchContent_Declare(solanaceae_util
GIT_REPOSITORY https://github.com/Green-Sky/solanaceae_util.git
GIT_TAG master
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(solanaceae_util)
endif()
if (NOT TARGET solanaceae_contact)
FetchContent_Declare(solanaceae_contact
GIT_REPOSITORY https://github.com/Green-Sky/solanaceae_contact.git
GIT_TAG master
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(solanaceae_contact)
endif()
if (NOT TARGET solanaceae_message3)
FetchContent_Declare(solanaceae_message3
GIT_REPOSITORY https://github.com/Green-Sky/solanaceae_message3.git
GIT_TAG master
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(solanaceae_message3)
endif()
if (NOT TARGET solanaceae_plugin)
FetchContent_Declare(solanaceae_plugin
GIT_REPOSITORY https://github.com/Green-Sky/solanaceae_plugin.git
GIT_TAG master
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(solanaceae_plugin)
endif()

11
plugins/CMakeLists.txt Normal file
View File

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.14...3.24 FATAL_ERROR)
add_library(plugin_bridge SHARED
./plugin_bridge.cpp
)
target_link_libraries(plugin_bridge PUBLIC
solanaceae_plugin
solanaceae_bridge
)

78
plugins/plugin_bridge.cpp Normal file
View File

@ -0,0 +1,78 @@
#include <solanaceae/plugin/solana_plugin_v1.h>
#include "../src/bridge.hpp"
#include <memory>
#include <iostream>
#define RESOLVE_INSTANCE(x) static_cast<x*>(solana_api->resolveInstance(#x))
#define PROVIDE_INSTANCE(x, p, v) solana_api->provideInstance(#x, p, static_cast<x*>(v))
static std::unique_ptr<Bridge> g_bridge = nullptr;
extern "C" {
SOLANA_PLUGIN_EXPORT const char* solana_plugin_get_name(void) {
return "Bridge";
}
SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_get_version(void) {
return SOLANA_PLUGIN_VERSION;
}
SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api) {
std::cout << "PLUGIN Bridge START()\n";
if (solana_api == nullptr) {
return 1;
}
Contact3Registry* cr;
RegistryMessageModel* rmm = nullptr;
ConfigModelI* conf = nullptr;
{ // make sure required types are loaded
cr = RESOLVE_INSTANCE(Contact3Registry);
rmm = RESOLVE_INSTANCE(RegistryMessageModel);
conf = RESOLVE_INSTANCE(ConfigModelI);
if (cr == nullptr) {
std::cerr << "PLUGIN Bridge missing Contact3Registry\n";
return 2;
}
if (rmm == nullptr) {
std::cerr << "PLUGIN Bridge missing RegistryMessageModel\n";
return 2;
}
if (conf == nullptr) {
std::cerr << "PLUGIN Bridge missing ConfigModelI\n";
return 2;
}
}
// static store, could be anywhere tho
// construct with fetched dependencies
g_bridge = std::make_unique<Bridge>(*cr, *rmm, *conf);
// register types
PROVIDE_INSTANCE(Bridge, "Bridge", g_bridge.get());
return 0;
}
SOLANA_PLUGIN_EXPORT void solana_plugin_stop(void) {
std::cout << "PLUGIN Bridge STOP()\n";
g_bridge.reset();
}
SOLANA_PLUGIN_EXPORT void solana_plugin_tick(float delta) {
(void)delta;
//std::cout << "PLUGIN Bridge TICK()\n";
g_bridge->iterate(delta);
}
} // extern C

13
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
add_library(solanaceae_bridge STATIC
./bridge.hpp
./bridge.cpp
)
target_compile_features(solanaceae_bridge PUBLIC cxx_std_17)
target_link_libraries(solanaceae_bridge PUBLIC
solanaceae_contact
solanaceae_message3
)

184
src/bridge.cpp Normal file
View File

@ -0,0 +1,184 @@
#include "./bridge.hpp"
#include <solanaceae/util/config_model.hpp>
#include <solanaceae/contact/components.hpp>
#include <solanaceae/message3/components.hpp>
#include <iostream>
namespace detail {
constexpr uint8_t nib_from_hex(char c) {
assert((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'));
if (c >= '0' && c <= '9') {
return static_cast<uint8_t>(c) - '0';
} else if (c >= 'a' && c <= 'f') {
return (static_cast<uint8_t>(c) - 'a') + 10u;
} else {
return 0u;
}
}
constexpr char nib_to_hex(uint8_t c) {
assert(c <= 0x0f);
if (c <= 0x09) {
return c + '0';
} else {
return (c - 10u) + 'a';
}
}
} // detail
static std::vector<uint8_t> hex2bin(const std::string_view str) {
assert(str.size() % 2 == 0);
std::vector<uint8_t> bin{};
bin.resize(str.size()/2, 0);
for (size_t i = 0; i < bin.size(); i++) {
bin[i] = detail::nib_from_hex(str[i*2]) << 4 | detail::nib_from_hex(str[i*2+1]);
}
return bin;
}
static std::string bin2hex(const std::vector<uint8_t> data) {
std::string str;
for (size_t i = 0; i < data.size(); i++) {
str.push_back(detail::nib_to_hex(data[i] >> 4));
str.push_back(detail::nib_to_hex(data[i] & 0x0f));
}
return str;
}
Bridge::Bridge(
Contact3Registry& cr,
RegistryMessageModel& rmm,
ConfigModelI& conf
) : _cr(cr), _rmm(rmm), _conf(conf) {
_rmm.subscribe(this, enumType::message_construct);
// load synced contacts (bridged groups)
std::map<std::string, size_t> tmp_name_to_id;
for (const auto [contact_id, vgroup_str] : _conf.entries_string("Bridge", "contact_to_vgroup")) {
const auto tmp_vgroup_str = std::string{vgroup_str.start, vgroup_str.start+vgroup_str.extend};
if (!tmp_name_to_id.count(tmp_vgroup_str)) {
tmp_name_to_id[tmp_vgroup_str] = _vgroups.size();
_vgroups.emplace_back();;
}
auto& v_group = _vgroups.at(tmp_name_to_id.at(tmp_vgroup_str));
auto& new_vgc = v_group.contacts.emplace_back();
new_vgc.c = {_cr, entt::null}; // this is annoying af
new_vgc.id = hex2bin(contact_id);
}
updateVGroups();
}
Bridge::~Bridge(void) {
}
void Bridge::iterate(float time_delta) {
_iterate_timer += time_delta;
if (_iterate_timer >= 10.f) {
_iterate_timer = 0.f;
updateVGroups();
}
}
void Bridge::updateVGroups(void) {
// fill in contacts, some contacts might be created late
for (size_t i_vg = 0; i_vg < _vgroups.size(); i_vg++) {
for (auto& vgc : _vgroups[i_vg].contacts) {
assert(!vgc.id.empty());
if (!vgc.c.valid()) {
// search
auto view = _cr.view<Contact::Components::TagBig, Contact::Components::ID>();
for (const auto c : view) {
if (view.get<Contact::Components::ID>(c).data == vgc.id) {
vgc.c = {_cr, c};
std::cout << "Bridge: found contact for vgroup\n";
break;
}
}
}
if (vgc.c.valid()) {
_c_to_vg[vgc.c] = i_vg;
}
}
}
}
bool Bridge::onEvent(const Message::Events::MessageConstruct& e) {
if (!e.e.valid()) {
return false; // how
}
if (!e.e.all_of<Message::Components::MessageText>()) {
return false; // non text message, skip
}
if (!e.e.all_of<Message::Components::ContactFrom, Message::Components::ContactTo>()) {
return false; // how
}
const auto contact_from = e.e.get<Message::Components::ContactFrom>().c;
if (_cr.any_of<Contact::Components::TagSelfStrong, Contact::Components::TagSelfWeak>(contact_from)) {
return false; // skip own messages
}
const auto contact_to = e.e.get<Message::Components::ContactTo>().c;
// if e.e <contact to> is in c to vg
const auto it = _c_to_vg.find(Contact3Handle{_cr, contact_to});
if (it == _c_to_vg.cend()) {
return false; // contact is not bridged
}
const auto& vgroup = _vgroups.at(it->second);
const auto& message_text = e.e.get<Message::Components::MessageText>().text;
const bool is_action = e.e.all_of<Message::Components::TagMessageIsAction>();
std::string from_str;
if (_cr.all_of<Contact::Components::Name>(contact_from)) {
const auto& name = _cr.get<Contact::Components::Name>(contact_from).name;
if (name.empty()) {
from_str += "<UNK";
} else {
from_str += "<";
from_str += name.substr(0, 16);
}
}
if (_cr.all_of<Contact::Components::ID>(contact_from)) {
// copy
auto id = _cr.get<Contact::Components::ID>(contact_from).data;
id.resize(3);
from_str += "#" + bin2hex(id);
}
from_str += "> ";
// for each c in vg not c...
for (const auto& other_vc : vgroup.contacts) {
if (other_vc.c == contact_to) {
continue; // skip self
}
// TODO: support fake/virtual contacts. true bridging
std::string relayed_message {from_str};
relayed_message += message_text;
_rmm.sendText(
other_vc.c,
relayed_message,
is_action
);
}
return false;
}

48
src/bridge.hpp Normal file
View File

@ -0,0 +1,48 @@
#pragma once
#include <cstdint>
#include <solanaceae/message3/registry_message_model.hpp>
#include <solanaceae/contact/contact_model3.hpp>
#include <vector>
#include <map>
// fwd
struct ConfigModelI;
class Bridge : public RegistryMessageModelEventI {
Contact3Registry& _cr;
RegistryMessageModel& _rmm;
ConfigModelI& _conf;
struct VirtualGroups {
struct VContact {
Contact3Handle c; // might be null
std::vector<uint8_t> id; // if contact appears, we check
};
std::vector<VContact> contacts;
// metadata/settings?
};
std::vector<VirtualGroups> _vgroups;
std::map<Contact3Handle, size_t> _c_to_vg;
float _iterate_timer {0.f};
public:
Bridge(
Contact3Registry& cr,
RegistryMessageModel& rmm,
ConfigModelI& conf
);
~Bridge(void);
void iterate(float time_delta);
private:
void updateVGroups(void);
protected: // mm
bool onEvent(const Message::Events::MessageConstruct& e) override;
};