diff --git a/include/config/config.hpp b/include/config/config.hpp index f614e7c..92057b2 100644 --- a/include/config/config.hpp +++ b/include/config/config.hpp @@ -2,6 +2,8 @@ #include +#include +#include #include #include #include @@ -33,24 +35,17 @@ namespace Config { /// Experimental flag for limiting the framerate of DXVK games. uint32_t e_fps_limit; - /// Atomic property to check if the configuration is valid or outdated. - std::shared_ptr valid; + /// Path to the configuration file. + std::filesystem::path config_file; + /// File timestamp of the configuration file + std::chrono::time_point timestamp; }; /// Active configuration. Must be set in main.cpp. extern Configuration activeConf; /// - /// Load the config file and create a file watcher. - /// - /// @param file The path to the configuration file. - /// - /// @throws std::runtime_error if an error occurs while loading the configuration file. - /// - void loadAndWatchConfig(const std::string& file); - - /// - /// Reread the configuration file while preserving the old configuration + /// Read the configuration file while preserving the previous configuration /// in case of an error. /// /// @param file The path to the configuration file. diff --git a/src/config/config.cpp b/src/config/config.cpp index 0041afd..659b71a 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -4,14 +4,9 @@ #include "config/default_conf.hpp" #include -#include -#include -#include #include #include #include -#include -#include #include #include @@ -21,19 +16,11 @@ #include #include #include -#include #include #include #include -#include #include -#include -#include -#include -#include -#include #include -#include using namespace Config; @@ -44,103 +31,6 @@ namespace { Configuration Config::activeConf{}; -namespace { - [[noreturn]] void thread( - const std::string& file, - const std::shared_ptr& valid) { - const int fd = inotify_init(); - if (fd < 0) - throw std::runtime_error("Failed to initialize inotify:\n" - "- " + std::string(strerror(errno))); - - const std::string parent = std::filesystem::path(file).parent_path().string(); - const int wd = inotify_add_watch(fd, parent.c_str(), - IN_MODIFY | IN_CLOSE_WRITE | IN_MOVE_SELF); - if (wd < 0) { - close(fd); - - throw std::runtime_error("Failed to add inotify watch for " + parent + ":\n" - "- " + std::string(strerror(errno))); - } - - // watch for changes - std::optional discard_until; - const std::string filename = std::filesystem::path(file).filename().string(); - - std::array buffer{}; - while (true) { - // poll fd - struct pollfd pfd{}; - pfd.fd = fd; - pfd.events = POLLIN; - const int pollRes = poll(&pfd, 1, 100); - if (pollRes < 0 && errno != EINTR) { - inotify_rm_watch(fd, wd); - close(fd); - - throw std::runtime_error("Error polling inotify events:\n" - "- " + std::string(strerror(errno))); - } - - // read fd if there are events - const ssize_t len = pollRes == 0 ? 0 : read(fd, buffer.data(), buffer.size()); - if (len <= 0 && errno != EINTR && pollRes > 0) { - inotify_rm_watch(fd, wd); - close(fd); - - throw std::runtime_error("Error reading inotify events:\n" - "- " + std::string(strerror(errno))); - } - - size_t i{}; - while (std::cmp_less(i, len)) { - auto* event = reinterpret_cast(&buffer.at(i)); - i += sizeof(inotify_event) + event->len; - if (event->len <= 0 || event->mask & IN_IGNORED) - continue; - - const std::string name(reinterpret_cast(event->name)); - if (name != filename) - continue; - - // stall a bit, then mark as invalid - discard_until.emplace(std::chrono::steady_clock::now() - + std::chrono::milliseconds(500)); - } - - auto now = std::chrono::steady_clock::now(); - if (discard_until.has_value() && now > *discard_until) { - discard_until.reset(); - - // mark config as invalid - valid->store(false, std::memory_order_release); - - // and wait until it has been marked as valid again - while (!valid->load(std::memory_order_acquire)) - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } - } - } -} - -void Config::loadAndWatchConfig(const std::string& file) { - globalConf.valid = std::make_shared(true); - updateConfig(file); - - // prepare config watcher - if (std::getenv("LSFG_NO_HOT_RELOAD")) - return; - - std::thread([file = file, valid = globalConf.valid]() { - try { - thread(file, valid); - } catch (const std::exception& e) { - std::cerr << "lsfg-vk: Error in config watcher thread:\n"; - std::cerr << "- " << e.what() << '\n'; - } - }).detach(); -} - namespace { /// Turn a string into a VkPresentModeKHR enum value. VkPresentModeKHR into_present(const std::string& mode) { @@ -195,7 +85,6 @@ namespace { } void Config::updateConfig(const std::string& file) { - globalConf.valid->store(true, std::memory_order_relaxed); if (!std::filesystem::exists(file)) { std::cerr << "lsfg-vk: Placing default configuration file at " << file << '\n'; const auto parent = std::filesystem::path(file).parent_path(); @@ -227,7 +116,8 @@ void Config::updateConfig(const std::string& file) { const toml::value globalTable = toml::find_or_default(toml, "global"); const Configuration global{ .dll = toml::find_or(globalTable, "dll", std::string()), - .valid = globalConf.valid // use the same validity flag + .config_file = file, + .timestamp = std::filesystem::last_write_time(file) }; // validate global configuration @@ -256,7 +146,8 @@ void Config::updateConfig(const std::string& file) { .hdr = toml::find_or(gameTable, "hdr_mode", false), .e_present = into_present(toml::find_or(gameTable, "experimental_present_mode", "")), .e_fps_limit = toml::find_or(gameTable, "experimental_fps_limit", 0U), - .valid = global.valid // only need a single validity flag + .config_file = file, + .timestamp = global.timestamp }; // validate the configuration @@ -279,8 +170,7 @@ Configuration Config::getConfig(const std::pair& name) .enable = true, .multiplier = 2, .flowScale = 1.0F, - .e_present = VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR, - .valid = std::make_shared(true) + .e_present = VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR }; const char* dll = std::getenv("LSFG_DLL_PATH"); diff --git a/src/context.cpp b/src/context.cpp index 0f747dc..e00804a 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -11,14 +11,16 @@ #include #include +#include #include #include #include #include -#include -#include -#include #include +#include +#include +#include +#include #include LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, @@ -27,8 +29,13 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, extent(extent) { // get updated configuration auto& conf = Config::activeConf; - if (!conf.valid->load(std::memory_order_relaxed)) { + if (!conf.config_file.empty() + && ( + !std::filesystem::exists(conf.config_file) + || conf.timestamp != std::filesystem::last_write_time(conf.config_file) + )) { std::cerr << "lsfg-vk: Rereading configuration, as it is no longer valid.\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // reread configuration const std::string file = Utils::getConfigFile(); diff --git a/src/hooks.cpp b/src/hooks.cpp index 72bf702..a4f7aed 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -8,13 +8,13 @@ #include #include +#include #include #include #include #include #include #include -#include #include #include @@ -261,7 +261,11 @@ namespace { try { // ensure config is valid auto& conf = Config::activeConf; - if (!conf.valid->load(std::memory_order_relaxed)) { + if (!conf.config_file.empty() + && ( + !std::filesystem::exists(conf.config_file) + || conf.timestamp != std::filesystem::last_write_time(conf.config_file) + )) { Layer::ovkQueuePresentKHR(queue, pPresentInfo); return VK_ERROR_OUT_OF_DATE_KHR; } diff --git a/src/main.cpp b/src/main.cpp index 2d3afcf..0e1543c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,7 +22,7 @@ namespace { // read configuration const std::string file = Utils::getConfigFile(); try { - Config::loadAndWatchConfig(file); + Config::updateConfig(file); } catch (const std::exception& e) { std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, IGNORING:\n"; std::cerr << "- " << e.what() << '\n';