From aed305da644310bfa7da4dd27deff15dde725822 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Fri, 6 Dec 2024 00:07:24 +0100 Subject: [PATCH] Base: Add Services and ServiceProvider This is intended to be used for intra-module communication. Modules can specify service that are then implemented by other modules. This way we can use features from for example part in Core without relying on the Part module explicitly. Base does provide Service definition - which is basically an abstract class with pure virtual methods. This service then can be implemented in other modules and accessed in runtime via ServiceProvider class that stores all implementations. ServiceProvider does store multiple implementations so in theory it is possible to use it to provide granular implementations. For example, part can provide CenterOfMass service that provides center of mass for part features, mesh can implement another one for meshes and then we can iterate over all implementations and find one that can provide center of --- src/Base/CMakeLists.txt | 4 +- src/Base/ServiceProvider.cpp | 31 +++++++ src/Base/ServiceProvider.h | 143 +++++++++++++++++++++++++++++ tests/src/Base/CMakeLists.txt | 1 + tests/src/Base/ServiceProvider.cpp | 110 ++++++++++++++++++++++ 5 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 src/Base/ServiceProvider.cpp create mode 100644 src/Base/ServiceProvider.h create mode 100644 tests/src/Base/ServiceProvider.cpp diff --git a/src/Base/CMakeLists.txt b/src/Base/CMakeLists.txt index a38b3ed60c..3159174c11 100644 --- a/src/Base/CMakeLists.txt +++ b/src/Base/CMakeLists.txt @@ -256,6 +256,7 @@ SET(FreeCADBase_CPP_SRCS Rotation.cpp RotationPyImp.cpp Sequencer.cpp + ServiceProvider.cpp SmartPtrPy.cpp Stream.cpp Swap.cpp @@ -319,6 +320,7 @@ SET(FreeCADBase_HPP_SRCS QtTools.h Reader.h Rotation.h + ServiceProvider.h Sequencer.h SmartPtrPy.h Stream.h @@ -356,7 +358,7 @@ IF (MSVC) ${FreeCADBase_SRCS} StackWalker.cpp StackWalker.h -) + ) ENDIF(MSVC) # Use external zipios++ if specified. diff --git a/src/Base/ServiceProvider.cpp b/src/Base/ServiceProvider.cpp new file mode 100644 index 0000000000..f1d583e69a --- /dev/null +++ b/src/Base/ServiceProvider.cpp @@ -0,0 +1,31 @@ + +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Kacper Donat * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "ServiceProvider.h" + +Base::ServiceProvider& Base::ServiceProvider::get() +{ + static Base::ServiceProvider instance; + return instance; +} diff --git a/src/Base/ServiceProvider.h b/src/Base/ServiceProvider.h new file mode 100644 index 0000000000..738c56d172 --- /dev/null +++ b/src/Base/ServiceProvider.h @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Kacper Donat * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef APP_SERVICE_PROVIDER_H +#define APP_SERVICE_PROVIDER_H + +#include + +#include +#include +#include +#include +#include +#include + +namespace Base +{ + +class BaseExport ServiceProvider +{ + struct ServiceDescriptor + { + std::string name; + std::any instance; + + template + T* get() const + { + return std::any_cast(instance); + } + }; + +public: + ServiceProvider() = default; + + /** + * Returns most recent implementation of service specified as T param. + * + * @tparam T Service interface + */ + template + T* provide() const + { + if (auto it = _implementations.find(typeid(T).name()); it != _implementations.end()) { + auto descriptors = it->second; + + if (descriptors.empty()) { + return nullptr; + } + + return descriptors.front().get(); + } + + return nullptr; + } + + /** + * Returns all implementations of service specified as T param. + * + * @tparam T Service interface + */ + template + std::list all() const + { + if (auto it = _implementations.find(typeid(T).name()); it != _implementations.end()) { + auto source = it->second; + + std::list result(source.size()); + + std::transform(source.begin(), + source.end(), + result.begin(), + [](const ServiceDescriptor& descriptor) { + return descriptor.get(); + }); + + return result; + } + + return {}; + } + + /** + * Adds new implementation of service T. + * + * @tparam T Service interface + */ + template + void implement(T* contract) + { + ServiceDescriptor descriptor {typeid(T).name(), contract}; + + _implementations[typeid(T).name()].push_front(descriptor); + } + + static ServiceProvider& get(); + +private: + std::map> _implementations; +}; + +template +T* provideImplementation() +{ + return ServiceProvider::get().provide(); +} + +template +std::list provideAllImplementations() +{ + return ServiceProvider::get().all(); +} + +template +void implementContract(T* implementation) +{ + ServiceProvider::get().implement(implementation); +} + +} // namespace Base + + +#endif // APP_SERVICE_PROVIDER_H diff --git a/tests/src/Base/CMakeLists.txt b/tests/src/Base/CMakeLists.txt index 2feb4d2eb0..2404b058a4 100644 --- a/tests/src/Base/CMakeLists.txt +++ b/tests/src/Base/CMakeLists.txt @@ -16,6 +16,7 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/Quantity.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Reader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Rotation.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ServiceProvider.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Stream.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TimeInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Tools.cpp diff --git a/tests/src/Base/ServiceProvider.cpp b/tests/src/Base/ServiceProvider.cpp new file mode 100644 index 0000000000..405c8cf134 --- /dev/null +++ b/tests/src/Base/ServiceProvider.cpp @@ -0,0 +1,110 @@ +#include +#include + +class SimpleService +{ +public: + virtual ~SimpleService() = default; + virtual std::string foo() = 0; + + SimpleService() = default; + + SimpleService(const SimpleService& other) = delete; + SimpleService(SimpleService&& other) noexcept = delete; + SimpleService& operator=(const SimpleService& other) = delete; + SimpleService& operator=(SimpleService&& other) noexcept = delete; +}; + +class FirstServiceImplementation final: public SimpleService +{ +public: + std::string foo() override + { + return "first"; + } +}; + +class SecondServiceImplementation final: public SimpleService +{ +public: + std::string foo() override + { + return "second"; + } +}; + +TEST(ServiceProvider, provideEmptyImplementation) +{ + // Arrange + Base::ServiceProvider serviceProvider; + + // Act + auto implementation = serviceProvider.provide(); + + // Assert + EXPECT_EQ(implementation, nullptr); +} + +TEST(ServiceProvider, provideEmptyImplementationList) +{ + // Arrange + Base::ServiceProvider serviceProvider; + + // Act + const auto implementations = serviceProvider.all(); + + // Assert + EXPECT_EQ(implementations.size(), 0); +} + +TEST(ServiceProvider, provideImplementation) +{ + // Arrange + Base::ServiceProvider serviceProvider; + + serviceProvider.implement(new FirstServiceImplementation); + + // Act + auto implementation = serviceProvider.provide(); + + // Assert + EXPECT_NE(implementation, nullptr); + EXPECT_EQ(implementation->foo(), "first"); +} + +TEST(ServiceProvider, provideLatestImplementation) +{ + // Arrange + Base::ServiceProvider serviceProvider; + + serviceProvider.implement(new FirstServiceImplementation); + serviceProvider.implement(new SecondServiceImplementation); + + // Act + auto implementation = serviceProvider.provide(); + + // Assert + EXPECT_NE(implementation, nullptr); + EXPECT_EQ(implementation->foo(), "second"); +} + +TEST(ServiceProvider, provideAllImplementations) +{ + // Arrange + Base::ServiceProvider serviceProvider; + + serviceProvider.implement(new FirstServiceImplementation); + serviceProvider.implement(new SecondServiceImplementation); + + // Act + auto implementations = serviceProvider.all(); + auto it = implementations.begin(); + + // Assert + // Implementations should be available in order from the most recent one + EXPECT_EQ((*it)->foo(), "second"); + ++it; + EXPECT_EQ((*it)->foo(), "first"); + ++it; + EXPECT_EQ(it, implementations.end()); +}