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
This commit is contained in:
Kacper Donat
2024-12-06 00:07:24 +01:00
committed by Chris Hennes
parent 752b5dcf68
commit aed305da64
5 changed files with 288 additions and 1 deletions

View File

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

View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2024 Kacper Donat <kacper@kadet.net> *
* *
* 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 *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#include "ServiceProvider.h"
Base::ServiceProvider& Base::ServiceProvider::get()
{
static Base::ServiceProvider instance;
return instance;
}

143
src/Base/ServiceProvider.h Normal file
View File

@@ -0,0 +1,143 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2024 Kacper Donat <kacper@kadet.net> *
* *
* 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 *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#ifndef APP_SERVICE_PROVIDER_H
#define APP_SERVICE_PROVIDER_H
#include <FCGlobal.h>
#include <algorithm>
#include <deque>
#include <map>
#include <string>
#include <any>
#include <list>
namespace Base
{
class BaseExport ServiceProvider
{
struct ServiceDescriptor
{
std::string name;
std::any instance;
template<typename T>
T* get() const
{
return std::any_cast<T*>(instance);
}
};
public:
ServiceProvider() = default;
/**
* Returns most recent implementation of service specified as T param.
*
* @tparam T Service interface
*/
template<typename T>
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<T>();
}
return nullptr;
}
/**
* Returns all implementations of service specified as T param.
*
* @tparam T Service interface
*/
template<typename T>
std::list<T*> all() const
{
if (auto it = _implementations.find(typeid(T).name()); it != _implementations.end()) {
auto source = it->second;
std::list<T*> result(source.size());
std::transform(source.begin(),
source.end(),
result.begin(),
[](const ServiceDescriptor& descriptor) {
return descriptor.get<T>();
});
return result;
}
return {};
}
/**
* Adds new implementation of service T.
*
* @tparam T Service interface
*/
template<typename T>
void implement(T* contract)
{
ServiceDescriptor descriptor {typeid(T).name(), contract};
_implementations[typeid(T).name()].push_front(descriptor);
}
static ServiceProvider& get();
private:
std::map<const char*, std::deque<ServiceDescriptor>> _implementations;
};
template<typename T>
T* provideImplementation()
{
return ServiceProvider::get().provide<T>();
}
template<typename T>
std::list<T*> provideAllImplementations()
{
return ServiceProvider::get().all<T>();
}
template<typename T>
void implementContract(T* implementation)
{
ServiceProvider::get().implement<T>(implementation);
}
} // namespace Base
#endif // APP_SERVICE_PROVIDER_H

View File

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

View File

@@ -0,0 +1,110 @@
#include <gtest/gtest.h>
#include <Base/ServiceProvider.h>
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<SimpleService>();
// Assert
EXPECT_EQ(implementation, nullptr);
}
TEST(ServiceProvider, provideEmptyImplementationList)
{
// Arrange
Base::ServiceProvider serviceProvider;
// Act
const auto implementations = serviceProvider.all<SimpleService>();
// Assert
EXPECT_EQ(implementations.size(), 0);
}
TEST(ServiceProvider, provideImplementation)
{
// Arrange
Base::ServiceProvider serviceProvider;
serviceProvider.implement<SimpleService>(new FirstServiceImplementation);
// Act
auto implementation = serviceProvider.provide<SimpleService>();
// Assert
EXPECT_NE(implementation, nullptr);
EXPECT_EQ(implementation->foo(), "first");
}
TEST(ServiceProvider, provideLatestImplementation)
{
// Arrange
Base::ServiceProvider serviceProvider;
serviceProvider.implement<SimpleService>(new FirstServiceImplementation);
serviceProvider.implement<SimpleService>(new SecondServiceImplementation);
// Act
auto implementation = serviceProvider.provide<SimpleService>();
// Assert
EXPECT_NE(implementation, nullptr);
EXPECT_EQ(implementation->foo(), "second");
}
TEST(ServiceProvider, provideAllImplementations)
{
// Arrange
Base::ServiceProvider serviceProvider;
serviceProvider.implement<SimpleService>(new FirstServiceImplementation);
serviceProvider.implement<SimpleService>(new SecondServiceImplementation);
// Act
auto implementations = serviceProvider.all<SimpleService>();
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());
}