Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/external plugins #23

Merged
merged 38 commits into from
Aug 9, 2024
Merged

Feature/external plugins #23

merged 38 commits into from
Aug 9, 2024

Conversation

nathanhhughes
Copy link
Collaborator

@Schmluk Finally clean enough to PR and review

@nathanhhughes nathanhhughes requested a review from Schmluk August 8, 2024 20:52
Copy link
Collaborator

@Schmluk Schmluk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Really like the new functionality and the care put into explaining how to use it! I left a few minor and understanding comments, otherwise good to go I think!

* @brief Populate factory methods from a provided shared object library
* @param library_path Relative or absolute path to library (without extension or lib prefix)
*
* Warning: loading modules or code from external libraries that are not linked into the current
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Very helpful!

config_utilities/include/config_utilities/factory.h Outdated Show resolved Hide resolved
if (map.find(type) != map.end()) {
if (!type_info.empty()) {
Logger::logError("Cannot register already existent type '" + type + "' for " + type_info + ".");
class ModuleRegistry {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: It might help to add brief docstrings to the different classes (what they are supposed to do / what's the difference to other ones) for easier reading into the code (there's a few parts that sound somewhat similar on a quick pass)

using Factory = FactoryMethod<BaseT, Args...>;
if (locked()) {
if (hasModule(key, type)) {
return false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to log a warning here or does this intentionally fail quietly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentionally silently fails (I'll add a note). The check is to avoid compiled-in factories that get re-registered in the external library (e.g., StdoutLogger)

static FactoryMethod<BaseT, Args...> getModule(const std::string& type,
const std::string& registration_info,
bool skip_first_arg = false) {
using Factory = FactoryMethod<BaseT, Args...>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: factories and factory methods are technically not the same thing (and typically similar but not identical patterns). I don't think people will be too confused here though.

config_utilities/src/external_registry.cpp Outdated Show resolved Hide resolved
}

void ModuleRegistry::removeModule(const ModuleInfo& key, const std::string& type) {
{ // module scope
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for these being scoped?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't feel like coming up with specific variable names for iter so the scope is to be able to use it twice

config::internal::Formatter(): {
'asl' (config::internal::AslFormatter),
},
config::internal::Logger(): {
'stdout' (config::internal::StdoutLogger),
'test_logger' (config::test::TestLogger),
},
config::test::Base(_, int): {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand the _ are skip-first arg registrations? It's not very clear to me how to read these (the initial idea of the design was to show the user how to invoke all these types/factories. Not clear to me how I would go about this for a Object(_)? Open to changes though 🙂

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, sorry meant to check with you about what you wanted here and then forgot. The _ is to mark that these are actually ObjectWithConfig registrations. The reason for the skip first is because otherwise you get YAML::Node as the first arg which looked ugly and was confusing (though now that I think about it, it think ObjectFactory::create would dispatch properly to the ObjectWithConfigFactory version if you called it with a YAML::Node arg first). I also tried * as an option (i.e., as a Any type), but didn't think it was as intuitive (the _ is meant to be like std::placeholder::_1). Another option might be ConfigT but I was a little nervous about that being confused for an instantiable type. I could also drop the extra arg entirely from the signature, but then you get two identical signatures with different modules next to each other which is also confusing

@@ -0,0 +1,119 @@
# Loading External Factories
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

instances_.clear();
}

LibraryGuard ExternalRegistry::load(const std::filesystem::path& library_path) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense / is it feasible to check whether the library to be loaded exists and is found by LD_PATH? That would allow importing libraries on a 'as available' basis, or warn the user why the library could not be found?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might have to be done by a try / catch instead of preemptively checking, but yeah that's a good point (and I should actually be handling boost failing here)

@nathanhhughes nathanhhughes merged commit 4978d96 into main Aug 9, 2024
2 checks passed
@nathanhhughes nathanhhughes deleted the feature/external_plugins branch August 9, 2024 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants