diff --git a/.clang-format b/.clang-format index 11d0dcd..e0958e5 100644 --- a/.clang-format +++ b/.clang-format @@ -1 +1,12 @@ BasedOnStyle: WebKit +QualifierAlignment: 'Custom' +QualifierOrder: [ + 'friend', + 'static', + 'constexpr', + 'inline', + 'volatile', + 'restrict', + 'const', + 'type', +] diff --git a/CMakeLists.txt b/CMakeLists.txt index fa5b942..c9e8d48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ option(PPPLUGIN_ENABLE_UNREACHABLE_SANITIZE "Enable compilation with unreachable sanitize flags" OFF) find_package(Boost 1.61.0 REQUIRED COMPONENTS headers filesystem) -find_package(Python 3.0 REQUIRED COMPONENTS Development) +find_package(Python 3.9...<3.12 REQUIRED COMPONENTS Development) find_package(Lua 5.2 REQUIRED) set(THREADS_PREFER_PTHREAD_FLAG ON) @@ -60,7 +60,6 @@ set(DEBUG_FLAGS "-g3") if(${PPPLUGIN_ENABLE_CPP17_COMPATIBILITY}) find_package(fmt 8.1.0 REQUIRED) set(CMAKE_CXX_STANDARD 17) - # TODO: generate C++17 compatible header files instead add_definitions("-DPPPLUGIN_CPP17_COMPATIBILITY") else() set(CMAKE_CXX_STANDARD 20) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 890a974..f18ec2e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -4,3 +4,5 @@ add_subdirectory(lua_plugin) add_subdirectory(multi_language_plugin) add_subdirectory(multi_return_lua_plugin) add_subdirectory(python_plugin) + +add_subdirectory(testing) # TODO: to be removed diff --git a/examples/lua_plugin/CMakeLists.txt b/examples/lua_plugin/CMakeLists.txt index 4d6708b..09c45f4 100644 --- a/examples/lua_plugin/CMakeLists.txt +++ b/examples/lua_plugin/CMakeLists.txt @@ -5,4 +5,6 @@ add_custom_command( TARGET lua_plugin_manager POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugin.lua - $) + $/plugin_1.lua + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugin.lua + $/plugin_2.lua) diff --git a/examples/lua_plugin/lua_plugin_manager.cpp b/examples/lua_plugin/lua_plugin_manager.cpp index 8cc8f9d..88a95b0 100644 --- a/examples/lua_plugin/lua_plugin_manager.cpp +++ b/examples/lua_plugin/lua_plugin_manager.cpp @@ -36,9 +36,11 @@ int main(int argc, char* argv[]) // only load files ending with ".lua" and execute in separate thread if (path.extension() == ".lua") { if (auto plugin = manager.loadLuaPlugin(path)) { - threads.emplace_back([plugin = std::move(*plugin)]() mutable { + threads.emplace_back([plugin = std::move(*plugin), plugin_number = threads.size()]() mutable { + // data race due to shared resource "stdout", + // but Lua plugins are otherwise thread-safe std::ignore = plugin.call("initialize"); - std::ignore = plugin.call("loop", "2"); + std::ignore = plugin.call("loop", plugin_number); }); } } diff --git a/examples/python_plugin/python_plugin_manager.cpp b/examples/python_plugin/python_plugin_manager.cpp index 6d79f72..f195cc6 100644 --- a/examples/python_plugin/python_plugin_manager.cpp +++ b/examples/python_plugin/python_plugin_manager.cpp @@ -41,7 +41,7 @@ int main(int argc, char* argv[]) if (plugin) { threads.emplace_back([plugin = std::move(*plugin), plugin_number = plugin_number++]() mutable { // ignore calling errors - std::ignore = plugin.call("initialize", plugin_number); + plugin.call("initialize", plugin_number).valueOrElse([](ppplugin::CallError error) { std::cerr << error.what() << std::endl; }); std::ignore = plugin.call("loop", std::to_string(plugin_number)); }); } else { diff --git a/examples/testing/CMakeLists.txt b/examples/testing/CMakeLists.txt new file mode 100644 index 0000000..b5b7e2e --- /dev/null +++ b/examples/testing/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(testing testing.cpp) +target_link_libraries(testing ${LIBRARY_TARGET}) + +add_custom_command( + TARGET testing + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugin.py + $/plugin_1.py + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugin.py + $/plugin_2.py) diff --git a/examples/testing/plugin.py b/examples/testing/plugin.py new file mode 100644 index 0000000..27aa021 --- /dev/null +++ b/examples/testing/plugin.py @@ -0,0 +1,14 @@ +import time + + +def initialize(something): + print("initialize: ", something) + + +def loop(something): + while True: + print("loop: ", something) + time.sleep(1) + + +print("loading") diff --git a/examples/testing/testing.cpp b/examples/testing/testing.cpp new file mode 100644 index 0000000..7f86a3c --- /dev/null +++ b/examples/testing/testing.cpp @@ -0,0 +1,64 @@ +#include "plugin_manager.h" +#include "python/plugin.h" +#include "python/python_interpreter.h" + +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + std::vector threads; + try { + if (argc < 1) { + return -1; + } + // path to plugins can be passed via command line; + // if no path was specified, the location of the executable is used instead + std::filesystem::path plugin_dir; + if (argc < 2) { + plugin_dir = std::filesystem::path { argv[0] }.parent_path(); + } else { + plugin_dir = std::filesystem::path { argv[1] }; + } + ppplugin::GenericPluginManager const manager; + int plugin_number {}; + std::vector const plugins; + + // checkout: https://stackoverflow.com/questions/26061298/python-multi-thread-multi-interpreter-c-api#26570708 + // recursively traverse filesystem to find scripts + const std::filesystem::recursive_directory_iterator dir_iterator { plugin_dir }; + for (const auto& entry : dir_iterator) { + if (!entry.is_regular_file()) { + continue; + } + const auto& path = entry.path(); + // only load files ending with ".lua" and execute in separate thread + if (path.extension() == ".py") { + ppplugin::PythonInterpreter interpreter; + if (auto error = interpreter.load(path.string())) { + std::cerr << "failed to load " << path << '\n'; + // return -1; + } + threads.emplace_back([manager, path, plugin_number = plugin_number++, interpreter = std::move(interpreter)]() mutable { + interpreter.call("initialize", std::hash {}(std::this_thread::get_id())); + interpreter.call("loop", std::hash {}(std::this_thread::get_id())); + }); + } + } + for (auto& thread : threads) { + thread.join(); + } + } catch (const std::exception& exception) { + std::cerr << "A fatal error occurred: '" << exception.what() << "'\n"; + return 1; + } catch (...) { + std::cerr << "An unknown fatal error occurred!"; + return 1; + } + return 0; +} diff --git a/include/errors.h b/include/errors.h index 01d69da..635984e 100644 --- a/include/errors.h +++ b/include/errors.h @@ -47,7 +47,7 @@ class CallError { { } - [[nodiscard]] CallError error() const { return error_; } + [[nodiscard]] CallError::Code error() const { return error_; } [[nodiscard]] const std::string& what() const { return what_; } #ifndef PPPLUGIN_CPP17_COMPATIBILITY [[nodiscard]] const std::source_location& location() const diff --git a/include/lua/lua_state.h b/include/lua/lua_state.h index 3bc80f1..a908639 100644 --- a/include/lua/lua_state.h +++ b/include/lua/lua_state.h @@ -134,7 +134,12 @@ class LuaState { std::optional> popFunction(bool always_pop = false); void pushOne(double value); - void pushOne(int value); + void pushOne(unsigned int value) { pushOne(static_cast(value)); } + void pushOne(int value) { pushOne(static_cast(value)); } + void pushOne(unsigned long value) { pushOne(static_cast(value)); } + void pushOne(long value) { pushOne(static_cast(value)); } + void pushOne(unsigned long long value) { pushOne(static_cast(value)); } + void pushOne(long long value); void pushOne(const char* value); void pushOne(std::string_view value); void pushOne(bool value); @@ -259,7 +264,6 @@ auto LuaState::topFunction() } return std::optional { std::nullopt }; } - } // namespace ppplugin #endif // PPPLUGIN_LUA_STATE_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9a02424..872b264 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,9 @@ target_link_libraries( PUBLIC Python::Python PRIVATE ${LUA_LIBRARIES}) if(${PPPLUGIN_ENABLE_CPP17_COMPATIBILITY}) + # TODO: generate C++17 compatible header files instead + # target_compile_options(${LIBRARY_TARGET} PUBLIC + # "-DPPPLUGIN_CPP17_COMPATIBILITY") # TODO: fix target_link_libraries(${LIBRARY_TARGET} PUBLIC fmt::fmt) endif() target_include_directories( diff --git a/src/boost_dll_loader.cpp b/src/boost_dll_loader.cpp index 62fc600..859e1c7 100644 --- a/src/boost_dll_loader.cpp +++ b/src/boost_dll_loader.cpp @@ -10,6 +10,11 @@ #include namespace { +/** + * Helper function to determine if given function can be called in given library. + * + * @return std::nullopt if function is callable, otherwise an error + */ std::optional isCallable(const boost::dll::shared_library& library, const std::string& function_name) { if (!library.is_loaded()) { diff --git a/src/lua_script.cpp b/src/lua_script.cpp index 0ad710e..3169aeb 100644 --- a/src/lua_script.cpp +++ b/src/lua_script.cpp @@ -30,7 +30,8 @@ LuaScript::LuaScript() luaL_openlibs(state_.state()); state_.registerPanicHandler([](lua_State* state) -> int { auto error = LuaState::wrap(state).top(); - // TODO: don't throw because will cross library boundaries + // TODO: don't throw because will cross library boundaries; + // use global error state (thread-local or lock-protected) throw std::runtime_error(format("Lua PANIC: '{}'!", error.value_or("?"))); }); // TODO: setup lua_setwarnf diff --git a/src/lua_state.cpp b/src/lua_state.cpp index 6c75962..424a86c 100644 --- a/src/lua_state.cpp +++ b/src/lua_state.cpp @@ -77,7 +77,7 @@ void LuaState::pushOne(double value) lua_pushnumber(state(), value); } -void LuaState::pushOne(int value) +void LuaState::pushOne(long long value) { lua_pushinteger(state(), value); }