Skip to content

Commit

Permalink
Tweak plugin APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
albin-johansson committed Oct 24, 2023
1 parent 1e2ecda commit fa3c5b0
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 106 deletions.
47 changes: 39 additions & 8 deletions modules/core/inc/tactile/core/container/smart_ptr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,63 @@
#pragma once

#include <memory> // unique_ptr, shared_ptr, weak_ptr, default_delete, make_unique, make_shared
#include <utility> // forward, move

namespace tactile {

template <typename T, typename Deleter = std::default_delete<T>>
using Unique = std::unique_ptr<T, Deleter>;

template <typename T>
using FPDeleter = void (*)(T*);
using Shared = std::shared_ptr<T>;

/** \brief Unique pointer with a function pointer deleter, useful for externally managed pointers. */
template <typename T>
using UniqueForeign = Unique<T, FPDeleter<T>>;
using Weak = std::weak_ptr<T>;

template <typename T>
using Shared = std::shared_ptr<T>;
using DeleterFptr = void (*)(T*);

/** \brief Unique pointer with a function pointer deleter, useful for externally managed pointers. */
template <typename T>
using Weak = std::weak_ptr<T>;
using Managed = Unique<T, DeleterFptr<T>>;

using std::make_shared;
using std::make_unique;

template <typename T>
[[nodiscard]] auto make_unique_foreign(T* ptr, FPDeleter<T> deleter) -> UniqueForeign<T>
/**
* \brief Creates a managed object.
*
* \tparam T the type of the object to allocate.
* \tparam Args the types of the forwarded arguments.
*
* \param args the arguments that will be forwarded to an appropriate constructor.
*
* \return a managed pointer.
*/
template <typename T, typename... Args>
[[nodiscard]] auto make_managed(Args&&... args) -> Managed<T>
{
auto deleter = [](T* ptr) noexcept { delete ptr; };
return Managed<T> {new T {std::forward<Args>(args)...}, deleter};
}

/**
* \brief Converts a managed pointer to a derived type to one to a base type.
*
* \pre The deleter used by the provided managed pointer must call `operator delete`.
*
* \tparam Base the base type.
* \tparam Derived the derived type.
*
* \param managed the managed pointer that will be converted.
*
* \return a managed pointer.
*/
template <typename Base, std::derived_from<Base> Derived>
[[nodiscard]] auto managed_cast(Managed<Derived> managed) noexcept -> Managed<Base>
{
return UniqueForeign<T> {ptr, deleter};
auto deleter = [](Base* ptr) noexcept { delete dynamic_cast<Derived*>(ptr); };
return Managed<Base> {managed.release(), deleter};
}

} // namespace tactile
28 changes: 25 additions & 3 deletions modules/core/inc/tactile/core/platform/dynamic_library.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ class TACTILE_CORE_API IDynamicLibrary {
*/
[[nodiscard]]
virtual auto get_symbol(const char* name) const -> void* = 0;

/**
* \brief Returns the file path to the dynamic library binary.
*
* \return a file path.
*/
[[nodiscard]]
virtual auto get_path() const -> const FilePath& = 0;
};

template <typename FnType>
Expand All @@ -46,19 +54,33 @@ auto get_symbol(const IDynamicLibrary& library, const char* name) -> FnType
return reinterpret_cast<FnType>(library.get_symbol(name));
}

/**
* \brief Indicates whether a file is likely to be a dynamic library.
*
* \note This function merely provides a course heuristic useful for excluding files
* that are unlikely to be dynamic libraries. It works by simply checking the file
* extension of the specified file and compares the extension to established
* dynamic library file extensions for the current platform.
*
* \param path the path to the file to check.
*
* \return true if the file could be a dynamic library; false otherwise.
*/
[[nodiscard]]
TACTILE_CORE_API auto is_dll(const FilePath& path) -> bool;

/**
* \brief Attempts to load a dynamic library.
*
* \details This function makes use of the `dlopen` and `LoadModuleA` APIs on Linux/macOS
* and Windows, respectively. For other platforms, this function simply returns a
* null pointer.
*
* \param library_path the file path to the dynamic library.
* \param path the file path to the dynamic library.
*
* \return the loaded library, or a null pointer on failure.
*/
[[nodiscard]]
TACTILE_CORE_API auto load_library(const FilePath& library_path)
-> Unique<IDynamicLibrary>;
TACTILE_CORE_API auto load_library(const FilePath& path) -> Unique<IDynamicLibrary>;

} // namespace tactile
51 changes: 14 additions & 37 deletions modules/core/inc/tactile/core/plugin/plugin_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ using PluginDestroyFn = void (*)(IPlugin*); ///< Signature of a plugin "destruc
/**
* \brief Provides information about a dynamic library plugin.
*/
struct PluginInfo final {
String name; ///< The plugin name.
FilePath library_path; ///< The file path of the binary.
Unique<IDynamicLibrary> dll; ///< The dynamic library handle.
UniqueForeign<IPlugin> plugin {nullptr, nullptr}; ///< The plugin instance.
struct PluginData final {
Unique<IDynamicLibrary> dll; ///< The dynamic library handle.
Managed<IPlugin> plugin {nullptr, nullptr}; ///< The plugin instance.
};

/**
Expand All @@ -51,50 +49,29 @@ class TACTILE_CORE_API PluginManager final {
*/
void scan(const FilePath& dir);

/**
* \brief Returns the loaded plugin information.
*
* \return the plugin information data.
*/
[[nodiscard]]
auto get_plugins() -> Vector<PluginInfo>&;

/** \copydoc PluginManager::get_plugins */
[[nodiscard]]
auto get_plugins() const -> const Vector<PluginInfo>&;
auto get_plugins() -> Vector<PluginData>&;

/**
* \brief Indicates whether a file is likely to be a dynamic library.
*
* \note This function merely provides a course heuristic useful for excluding files
* that are unlikely to be dynamic libraries. It works by simply checking the file
* extension of the specified file and compares the extension to established
* dynamic library file extensions for the current platform.
*
* \param file the file to check.
*
* \return true if the file could be a dynamic library; false otherwise.
*/
[[nodiscard]]
static auto is_dll(const FilePath& file) -> bool;
auto get_plugins() const -> const Vector<PluginData>&;

/**
* \brief Obtains information about a plugin.
* \brief Tries to load a plugin from a dynamic library.
*
* \details This function will, on success, initialize all members of the `PluginInfo`
* struct. However, the plugin will not be explicitly initialized, i.e., you'll
* still need to call `IPlugin::on_load` (and `IPlugin::on_unload`) yourself.
* This makes it possible to selectively load plugins based on user input.
* \details Plugin libraries must provide two C-linkage functions:
* `tactile_create_plugin` and `tactile_destroy_plugin`. The signatures of
* these functions must be equivalent to `IPlugin*()` and `void(IPlugin*)`,
* respectively.
*
* \param path the file path to the dynamic library.
* \param lib the source dynamic library handle.
*
* \return the plugin information, or nothing on failure.
* \return a potentially null managed plugin pointer.
*/
[[nodiscard]]
static auto load_library_info(const FilePath& path) -> Maybe<PluginInfo>;
static auto load_plugin(const IDynamicLibrary& lib) -> Managed<IPlugin>;

private:
Vector<PluginInfo> mPlugins;
Vector<PluginData> mPlugins;
};

} // namespace tactile
68 changes: 55 additions & 13 deletions modules/core/src/tactile/core/platform/dynamic_library.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

#include "tactile/core/platform/dynamic_library.hpp"

#include <utility> // move

#include "tactile/core/debug/log/logger.hpp"

#if TACTILE_OS_LINUX || TACTILE_OS_APPLE
#include <dlfcn.h>
#endif
Expand All @@ -20,10 +24,12 @@ class UnixDynamicLibrary final : public IDynamicLibrary {
TACTILE_DELETE_COPY(UnixDynamicLibrary);
TACTILE_DELETE_MOVE(UnixDynamicLibrary);

[[nodiscard]] static auto load(const char* path) -> Unique<UnixDynamicLibrary>
[[nodiscard]] static auto load(FilePath path) -> Unique<UnixDynamicLibrary>
{
if (void* handle = dlopen(path, RTLD_LAZY)) {
return Unique<UnixDynamicLibrary> {new UnixDynamicLibrary {handle}};
if (void* handle = dlopen(path.c_str(), RTLD_LAZY)) {
// We can't use make_unique here because of the private constructor.
return Unique<UnixDynamicLibrary> {
new UnixDynamicLibrary {std::move(path), handle}};
}

return nullptr;
Expand All @@ -42,11 +48,18 @@ class UnixDynamicLibrary final : public IDynamicLibrary {
return dlsym(mHandle, name);
}

[[nodiscard]] auto get_path() const -> const FilePath& override
{
return mPath;
}

private:
FilePath mPath;
void* mHandle;

explicit UnixDynamicLibrary(void* handle) noexcept
: mHandle {handle}
UnixDynamicLibrary(FilePath path, void* handle)
: mPath {std::move(path)},
mHandle {handle}
{}
};

Expand All @@ -59,10 +72,12 @@ class Win32DynamicLibrary final : public IDynamicLibrary {
TACTILE_DELETE_COPY(Win32DynamicLibrary);
TACTILE_DELETE_MOVE(Win32DynamicLibrary);

[[nodiscard]] static auto load(const char* path) -> Unique<Win32DynamicLibrary>
[[nodiscard]] static auto load(FilePath path) -> Unique<Win32DynamicLibrary>
{
if (const auto handle = LoadLibraryA(path)) {
return Unique<Win32DynamicLibrary> {new Win32DynamicLibrary {handle}};
if (const auto handle = LoadLibraryA(path.c_str())) {
// We can't use make_unique here because of the private constructor.
return Unique<Win32DynamicLibrary> {
new Win32DynamicLibrary {std::move(path), handle}};
}

return nullptr;
Expand All @@ -81,25 +96,52 @@ class Win32DynamicLibrary final : public IDynamicLibrary {
return static_cast<void*>(GetProcAddress(mHandle, name));
}

[[nodiscard]] auto get_path() const -> const FilePath& override
{
return mPath;
}

private:
FilePath mPath;
HMODULE mHandle;

explicit Win32DynamicLibrary(HMODULE handle) noexcept
: mHandle {handle}
explicit Win32DynamicLibrary(FilePath path, HMODULE handle)
: mPath {std::move(path)},
mHandle {handle}
{}
};

#endif // TACTILE_OS_WINDOWS

auto load_library(const FilePath& library_path) -> Unique<IDynamicLibrary>
auto is_dll(const FilePath& path) -> bool
{
const auto extension = path.extension();

if constexpr (kIsLinux) {
return extension == ".so";
}
else if constexpr (kIsApple) {
return extension == ".so" || extension == ".dylib";
}
else if constexpr (kIsWindows) {
return extension == ".dll";
}

TACTILE_LOG_WARN("Unknown platform, cannot determine valid DLL extensions");
return false;
}

auto load_library(const FilePath& path) -> Unique<IDynamicLibrary>
{
Unique<IDynamicLibrary> library;

if (is_dll(path)) {
#if TACTILE_OS_LINUX || TACTILE_OS_APPLE
library = UnixDynamicLibrary::load(library_path.c_str());
library = UnixDynamicLibrary::load(path);
#elif TACTILE_OS_WINDOWS
library = Win32DynamicLibrary::load(library_path.string().c_str());
library = Win32DynamicLibrary::load(path);
#endif
}

return library;
}
Expand Down
Loading

0 comments on commit fa3c5b0

Please sign in to comment.