diff --git a/src/shared/utils/DependencyManager/DependencyManager.cpp b/src/shared/utils/DependencyManager/DependencyManager.cpp index fe4d3357452d98ded20b57d0de23be2c188db6a8..df91e884219661e6dd620c72f1c2bf984c5eabfb 100644 --- a/src/shared/utils/DependencyManager/DependencyManager.cpp +++ b/src/shared/utils/DependencyManager/DependencyManager.cpp @@ -22,33 +22,26 @@ #include "DependencyManager.h" -#include <cxxabi.h> #include <fmt/format.h> using namespace Boardcore; -// Simple utility function to demangle type infos -std::string type_name_demangled(const std::type_info& info) -{ - char* demangled = - abi::__cxa_demangle(info.name(), nullptr, nullptr, nullptr); - std::string demangled2{demangled}; - std::free(demangled); - - return demangled2; -} - void DependencyManager::graphviz(std::ostream& os) { os << "digraph {" << std::endl; + // First print out all of the nodes + for (auto& module : modules) + { + os << fmt::format(" \"{}\"", module.first) << std::endl; + } + + // Then print out the arcs for (auto& module : modules) { for (auto& dep : module.second.deps) { - os << fmt::format(" \"{}({})\" -> \"{}({})\"", module.second.name, - module.second.impl, modules[dep].name, - modules[dep].impl) + os << fmt::format(" \"{}\" -> \"{}\"", module.first, dep) << std::endl; } } @@ -62,9 +55,9 @@ bool DependencyManager::inject() for (auto& module : modules) { - LOG_INFO(logger, "Configuring [{}]...", module.second.name); - DependencyInjector injector{*this, module.second}; - module.second.ptr->inject(injector); + LOG_INFO(logger, "Configuring [{}]...", module.first); + DependencyInjector injector{*this, module}; + module.second.injectable->inject(injector); } if (load_success) @@ -79,37 +72,41 @@ bool DependencyManager::inject() return load_success; } -bool DependencyManager::insertImpl(Injectable* ptr, - const std::type_info& module_info, - const std::type_info& impl_info) +bool DependencyManager::insertImpl(void* raw, Injectable* injectable, + std::string name) { - // Early check to see if ptr is nullptr, fail if that's the case - if (ptr == nullptr) - return false; - - auto idx = std::type_index{module_info}; - auto module_name = type_name_demangled(module_info); - auto impl_name = type_name_demangled(impl_info); - - return modules.insert({idx, ModuleInfo{ptr, module_name, impl_name, {}}}) + return modules.insert({std::move(name), ModuleInfo{raw, injectable, {}}}) .second; } -Injectable* DependencyInjector::getImpl(const std::type_info& module_info) +void* DependencyManager::getImpl(const std::string& name) { - auto idx = std::type_index{module_info}; - auto iter = manager.modules.find(idx); - if (iter == manager.modules.end()) + auto iter = modules.find(name); + if (iter == modules.end()) { - manager.load_success = false; - - std::string module_name = type_name_demangled(module_info); - LOG_ERR(logger, "[{}] requires [{}], but the latter is not present", - info.name, module_name); - return nullptr; } + else + { + return iter->second.raw; + } +} - info.deps.push_back(idx); - return iter->second.ptr; +void* DependencyInjector::getImpl(const std::string& name) +{ + void* ptr = manager.getImpl(name); + if (ptr == nullptr) + { + // Get failed, signal inject failure and log it + manager.load_success = false; + LOG_ERR(logger, "[{}] requires [{}], which doesn't exist", info.first, + name); + return nullptr; + } + else + { + // Get successful, add it to the dependencies + info.second.deps.push_back(name); + return ptr; + } } diff --git a/src/shared/utils/DependencyManager/DependencyManager.h b/src/shared/utils/DependencyManager/DependencyManager.h index 4a1826b2574c4c26c57747f12b5768bc904a6349..5f65095b35be0a5d669a49e85e871d6e5ca35a99 100644 --- a/src/shared/utils/DependencyManager/DependencyManager.h +++ b/src/shared/utils/DependencyManager/DependencyManager.h @@ -24,6 +24,7 @@ #include <diagnostic/PrintLogger.h> +#include <numeric> #include <ostream> #include <string> #include <typeindex> @@ -31,6 +32,8 @@ #include <unordered_map> #include <vector> +#include "TypeName.h" + namespace Boardcore { @@ -71,8 +74,9 @@ public: * class MyDependency1 : public Injectable {}; * * // Abstracting direct dependencies with a common interface - * class MyDependency2Iface {}; - * class MyDependency2 : public Injectable, public MyDependency2Iface {}; + * class MyDependency2Iface : public Injectable {}; + * class MyDependency2 : public + * InjectableWithDeps<InjectableBase<MyDependency2Iface>> {}; * * // A simple dependant (which can become a dependency itself) * class MyDependant : public InjectableWithDeps<MyDependency1, @@ -81,9 +85,6 @@ public: * DependencyManager dependency_mgr; * * // Initialize the dependencies - * MyDependency1 *dep1 = ; - * MyDependency2Iface *dep2 = new MyDependency2(); - * * dependency_mgr.insert<MyDependency1>(new MyDependency1()); * dependency_mgr.insert<MyDependency2Iface>(new MyDependency2()); * dependency_mgr.insert<MyDependant>(new MyDependant()); @@ -102,12 +103,12 @@ class DependencyManager private: struct ModuleInfo { - Injectable *ptr; - // Name of the module interface - std::string name; - // Name of the actual concrete implementation of this module interface - std::string impl; - std::vector<std::type_index> deps; + void *raw; ///< Pointer to the dependency's concrete type, returned + ///< when retrieving this dependency + Injectable *injectable; ///< Pointer to the dependency as an + ///< Injectable, needed for dynamic dispatching + ///< of the inject method + std::vector<std::string> deps; ///< List of dependencies }; public: @@ -116,14 +117,18 @@ public: /** * @brief Insert a new dependency. * + * @note If T is not Injectable the compiler will fail to find this method! + * * @param dependency Injectable to insert in the DependencyManager. * @returns True if successful, false otherwise. */ - template <typename T> + template <typename T, typename = std::enable_if_t< + std::is_base_of<Injectable, T>::value>> [[nodiscard]] bool insert(T *dependency) { - return insertImpl(dynamic_cast<Injectable *>(dependency), typeid(T), - typeid(*dependency)); + return insertImpl(reinterpret_cast<void *>(dependency), + static_cast<Injectable *>(dependency), + Boardcore::typeName<T>()); } /** @@ -142,15 +147,17 @@ public: [[nodiscard]] bool inject(); private: - [[nodiscard]] bool insertImpl(Injectable *ptr, - const std::type_info &module_info, - const std::type_info &impl_info); + [[nodiscard]] bool insertImpl(void *raw, Injectable *injectable, + std::string name); + + void *getImpl(const std::string &name); Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("DependencyManager"); bool load_success = true; - std::unordered_map<std::type_index, ModuleInfo> modules; + // Maps from interface type name to ModuleInfo + std::unordered_map<std::string, ModuleInfo> modules; }; /** @@ -161,8 +168,9 @@ class DependencyInjector friend class DependencyManager; private: - DependencyInjector(DependencyManager &manager, - DependencyManager::ModuleInfo &info) + DependencyInjector( + DependencyManager &manager, + std::pair<const std::string, DependencyManager::ModuleInfo> &info) : manager(manager), info(info) { } @@ -177,17 +185,17 @@ public: template <typename T> T *get() { - return dynamic_cast<T *>(getImpl(typeid(T))); + return reinterpret_cast<T *>(getImpl(Boardcore::typeName<T>())); } private: - Injectable *getImpl(const std::type_info &module_info); + void *getImpl(const std::string &name); Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("DependencyManager"); DependencyManager &manager; - DependencyManager::ModuleInfo &info; + std::pair<const std::string, DependencyManager::ModuleInfo> &info; }; namespace DependencyManagerDetails @@ -251,21 +259,86 @@ struct Contains<T, Type, Types...> } // namespace DependencyManagerDetails +template <typename T> +struct InjectableBase +{ +}; + +/** + * @brief Base class for an Injectable with dependencies. + */ template <typename... Types> class InjectableWithDeps : public Injectable { +protected: + /** + * Alias of the super class, to be used in derived classes in the + * constructor or when overriding methods + */ + using Super = InjectableWithDeps<Types...>; + public: virtual void inject(DependencyInjector &injector) override { storage.inject(injector); } - template <typename T> + /** + * @brief Get one of the modules in Types. + * + * @note If T is not inside Types... the compiler will fail to find this + * method! + */ + template <typename T, + typename = std::enable_if_t< + DependencyManagerDetails::Contains<T, Types...>::value>> T *getModule() { - static_assert(DependencyManagerDetails::Contains<T, Types...>::value, - "Dependency T is not present in the dependencies"); + return storage.template get<T>(); + } + +private: + DependencyManagerDetails::Storage<Types...> storage; +}; + +/** + * @brief Base class for an Injectable with dependencies and an Injectable + * superclass. + */ +template <typename Base, typename... Types> +class InjectableWithDeps<InjectableBase<Base>, Types...> : public Base +{ +protected: + /** + * Alias of the super class, to be used in derived classes in the + * constructor or when overriding methods + */ + using Super = InjectableWithDeps<InjectableBase<Base>, Types...>; + +public: + using InjectableSuper = Base; + using Base::Base; ///< Inherit constructors from Base + + static_assert(std::is_base_of<Injectable, Base>::value, + "Base must be Injectable"); + + virtual void inject(DependencyInjector &injector) override + { + Base::inject(injector); + storage.inject(injector); + } + /** + * @brief Get one of the modules in Types. + * + * @note If T is not inside Types... the compiler will fail to find this + * method! + */ + template <typename T, + typename = std::enable_if_t< + DependencyManagerDetails::Contains<T, Types...>::value>> + T *getModule() + { return storage.template get<T>(); } diff --git a/src/shared/utils/DependencyManager/TypeName.h b/src/shared/utils/DependencyManager/TypeName.h new file mode 100644 index 0000000000000000000000000000000000000000..0d7f10ff64c071919b2c55b1c08582c70fe06b63 --- /dev/null +++ b/src/shared/utils/DependencyManager/TypeName.h @@ -0,0 +1,80 @@ +/* Copyright (c) 2024 Skyward Experimental Rocketry + * Authors: Davide Mor + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <string> + +namespace Boardcore +{ + +namespace TypeNameDetails +{ + +// This needs to be in its own namespace so that types in Boardcore:: don't get +// the namespace cut off + +template <typename T> +std::string typeName() +{ + + // taken from + // https://github.com/Manu343726/ctti/blob/master/include/ctti/detail/pretty_function.hpp + +#if defined(__clang__) +// clang required slightly different logic, but should be supported, keep it +// like this for now +#error "Clang does not yet support typeName" +#elif defined(__GNUC__) || defined(__GNUG__) +#define SKYWARD_PRETTY_FUNCTION __PRETTY_FUNCTION__ +#define SKYWARD_PRETTY_FUNCTION_PREFIX \ + "std::string Boardcore::TypeNameDetails::typeName() [with T = " +#define SKYWARD_PRETTY_FUNCTION_SUFFIX \ + "; std::string = std::__cxx11::basic_string<char>]" +#else +#error "Compiler does not support typeName" +#endif + +#define SKYWARD_PRETTY_FUNCTION_PREFIX_LEN \ + (sizeof(SKYWARD_PRETTY_FUNCTION_PREFIX) - 1) +#define SKYWARD_PRETTY_FUNCTION_SUFFIX_LEN \ + (sizeof(SKYWARD_PRETTY_FUNCTION_SUFFIX) - 1) + + std::string pretty_function{SKYWARD_PRETTY_FUNCTION}; + return std::string{ + pretty_function.begin() + SKYWARD_PRETTY_FUNCTION_PREFIX_LEN, + pretty_function.end() - SKYWARD_PRETTY_FUNCTION_SUFFIX_LEN}; + +#undef SKYWARD_PRETTY_FUNCTION +#undef SKYWARD_PRETTY_FUNCTION_PREFIX +#undef SKYWARD_PRETTY_FUNCTION_SUFFIX +#undef SKYWARD_PRETTY_FUNCTION_PREFIX_LEN +#undef SKYWARD_PRETTY_FUNCTION_SUFFIX_LEN +} + +} // namespace TypeNameDetails + +template <typename T> +std::string typeName() +{ + return TypeNameDetails::typeName<T>(); +} + +} // namespace Boardcore \ No newline at end of file diff --git a/src/tests/catch/test-dependencymanager.cpp b/src/tests/catch/test-dependencymanager.cpp index 863e87f5123f1529c438a5d2e783b12a60ba7866..04989e34eaf9b3e4047088ee96f478c96d7b7156 100644 --- a/src/tests/catch/test-dependencymanager.cpp +++ b/src/tests/catch/test-dependencymanager.cpp @@ -23,6 +23,7 @@ #include <utils/DependencyManager/DependencyManager.h> #include <catch2/catch.hpp> +#include <iostream> using namespace Boardcore; @@ -46,7 +47,7 @@ private: bool value = false; }; -class B : public Injectable +class B : public InjectableWithDeps<A> { public: B() {} @@ -55,21 +56,18 @@ public: bool bong_b() { return value; } - void inject(DependencyInjector &getter) { a = getter.get<A>(); } - private: - A *a = nullptr; bool value = false; }; -class CIface +class CIface : public Injectable { public: virtual void bing_c() = 0; virtual bool bong_c() = 0; }; -class C : public CIface, public InjectableWithDeps<A, B> +class C : public InjectableWithDeps<InjectableBase<CIface>, A, B> { public: void bing_c() override @@ -83,21 +81,81 @@ private: bool value = false; }; -class D : public Injectable +class D : public InjectableWithDeps<CIface> { public: - void bing_d() { value = c->bong_c(); } + void bing_d() { value = getModule<CIface>()->bong_c(); } bool bong_d() { return value; } - void inject(DependencyInjector &getter) { c = getter.get<CIface>(); } - private: - CIface *c = nullptr; bool value = false; }; + +class E : public Injectable +{ +public: + int get_answer() { return 42; } +}; + +class F : public Injectable +{ +public: + int get_true_answer() { return 69; } +}; + +class G : public InjectableWithDeps<E> +{ +public: + virtual int get_truest_answer() { return getModule<E>()->get_answer(); } +}; + +class H : public InjectableWithDeps<InjectableBase<G>, F> +{ +public: + int get_truest_answer() override + { + return getModule<F>()->get_true_answer() + G::get_truest_answer(); + } +}; + +class I : public InjectableWithDeps<G> +{ +public: + int get_ultimate_true_answer() + { + return getModule<G>()->get_truest_answer(); + } +}; + +namespace Inner +{ +template <typename T, typename U> +struct Pair +{ +}; +} // namespace Inner + } // namespace Boardcore +TEST_CASE("DependencyManager - TypeName") +{ + + REQUIRE(Boardcore::typeName<Boardcore::A>() == "Boardcore::A"); + REQUIRE(Boardcore::typeName<Boardcore::B>() == "Boardcore::B"); + REQUIRE(Boardcore::typeName<Boardcore::CIface>() == "Boardcore::CIface"); + REQUIRE(Boardcore::typeName<Boardcore::C>() == "Boardcore::C"); + REQUIRE(Boardcore::typeName<Boardcore::D>() == "Boardcore::D"); + REQUIRE(Boardcore::typeName<Boardcore::E>() == "Boardcore::E"); + REQUIRE(Boardcore::typeName<Boardcore::F>() == "Boardcore::F"); + REQUIRE(Boardcore::typeName<Boardcore::G>() == "Boardcore::G"); + REQUIRE(Boardcore::typeName<Boardcore::H>() == "Boardcore::H"); + REQUIRE(Boardcore::typeName<Boardcore::I>() == "Boardcore::I"); + REQUIRE(Boardcore::typeName< + Boardcore::Inner::Pair<Boardcore::A, Boardcore::B>>() == + "Boardcore::Inner::Pair<Boardcore::A, Boardcore::B>"); +} + TEST_CASE("DependencyManager - Circular dependencies") { DependencyManager manager; @@ -105,8 +163,8 @@ TEST_CASE("DependencyManager - Circular dependencies") Boardcore::A *a = new Boardcore::A(); Boardcore::B *b = new Boardcore::B(); - REQUIRE(manager.insert<Boardcore::A>(a)); - REQUIRE(manager.insert<Boardcore::B>(b)); + REQUIRE(manager.insert(a)); + REQUIRE(manager.insert(b)); REQUIRE(manager.inject()); a->bing_a(true); @@ -120,6 +178,8 @@ TEST_CASE("DependencyManager - Circular dependencies") b->bing_b(false); REQUIRE(!b->bong_b()); + + manager.graphviz(std::cout); } TEST_CASE("DependencyManager - Virtual Dependencies") @@ -152,6 +212,8 @@ TEST_CASE("DependencyManager - Virtual Dependencies") REQUIRE(c->bong_c()); d->bing_d(); REQUIRE(d->bong_d()); + + manager.graphviz(std::cout); } TEST_CASE("DependencyManager - Inject fail") @@ -160,6 +222,37 @@ TEST_CASE("DependencyManager - Inject fail") Boardcore::A *a = new Boardcore::A(); - REQUIRE(manager.insert<Boardcore::A>(a)); + REQUIRE(manager.insert(a)); REQUIRE_FALSE(manager.inject()); } + +TEST_CASE("DependencyManager - Insert two instances fail") +{ + DependencyManager manager; + + Boardcore::A *a1 = new Boardcore::A(); + Boardcore::A *a2 = new Boardcore::A(); + + REQUIRE(manager.insert(a1)); + REQUIRE_FALSE(manager.insert(a2)); +} + +TEST_CASE("DependencyManager - Dependency tree") +{ + DependencyManager manager; + + Boardcore::E *e = new Boardcore::E(); + Boardcore::F *f = new Boardcore::F(); + Boardcore::H *h = new Boardcore::H(); + Boardcore::I *i = new Boardcore::I(); + + REQUIRE(manager.insert(e)); + REQUIRE(manager.insert(f)); + REQUIRE(manager.insert<Boardcore::G>(h)); + REQUIRE(manager.insert(i)); + REQUIRE(manager.inject()); + + REQUIRE(i->get_ultimate_true_answer() == 111); + + manager.graphviz(std::cout); +} \ No newline at end of file