include/DetourModKit/config.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_CONFIG_HPP | ||
| 2 | #define DETOURMODKIT_CONFIG_HPP | ||
| 3 | |||
| 4 | /** | ||
| 5 | * @file config.hpp | ||
| 6 | * @brief INI-backed configuration, hot reload, and INI-driven input combo bindings. | ||
| 7 | * @details Config is fail-soft, and no call returns an error for a missing or malformed value. A missing key or an | ||
| 8 | * empty bool value applies the registered default with no log record. A malformed int, float, or bool value | ||
| 9 | * applies the default, and a combo value with no parsable combo yields an empty list. Each logs a Warning, | ||
| 10 | * except the empty and "NONE" combo opt-outs. | ||
| 11 | * @note Thread safety: every setter must be reentrant and thread-safe. A setter can call the bind family and log_all(). | ||
| 12 | * In a load() or reload() pass, a setter call to load(), reload(), or disable_auto_reload() is refused. A setter | ||
| 13 | * call to clear() in a pass is refused except on the reload-servicer thread. | ||
| 14 | * @warning `[B-100]` Run load(), reload(), registration, and enable_auto_reload() outside the loader lock. These calls | ||
| 15 | * allocate, and enable_auto_reload() creates the watcher thread. The loader-lock teardown path detaches the | ||
| 16 | * watcher without a wait. `ConfigWatcherLoaderLockTest.*` pins the boundary. | ||
| 17 | */ | ||
| 18 | |||
| 19 | #include "DetourModKit/input.hpp" | ||
| 20 | |||
| 21 | #include <atomic> | ||
| 22 | #include <chrono> | ||
| 23 | #include <concepts> | ||
| 24 | #include <cstdint> | ||
| 25 | #include <functional> | ||
| 26 | #include <optional> | ||
| 27 | #include <string> | ||
| 28 | #include <string_view> | ||
| 29 | |||
| 30 | namespace DetourModKit | ||
| 31 | { | ||
| 32 | namespace config | ||
| 33 | { | ||
| 34 | /** @brief Outcome of enable_auto_reload(). */ | ||
| 35 | enum class AutoReloadStatus : std::uint8_t | ||
| 36 | { | ||
| 37 | /// The watcher started. | ||
| 38 | Started, | ||
| 39 | /// A watcher was already installed and stays in place. | ||
| 40 | AlreadyRunning, | ||
| 41 | /// No load() path is cached, as before any load() or after clear(), so there is no path to watch. | ||
| 42 | NoPriorLoad, | ||
| 43 | /// The watcher did not start, for example on a failed directory open or handshake, or a set unload latch. | ||
| 44 | StartFailed | ||
| 45 | }; | ||
| 46 | |||
| 47 | /// The value types that the atomic bind accepts. | ||
| 48 | template <typename T> | ||
| 49 | concept BindableScalar = std::same_as<T, int> || std::same_as<T, bool> || std::same_as<T, float>; | ||
| 50 | |||
| 51 | /** | ||
| 52 | * @brief Binds an integer INI key to a callback. | ||
| 53 | * @details Registration calls @p setter with @p default_value at once. Each load() or reload() pass that | ||
| 54 | * applies setters calls it again with the INI value. The value is decimal, or hex with a 0x prefix. An | ||
| 55 | * out-of-range value falls back to the default. | ||
| 56 | * @param display_name Not shown in log records. The records name @p key. | ||
| 57 | * @note Setup/control-plane only: registration may allocate and updates the config registry. | ||
| 58 | */ | ||
| 59 | void bind_int( | ||
| 60 | std::string_view section, | ||
| 61 | std::string_view key, | ||
| 62 | std::string_view display_name, | ||
| 63 | std::function<void(int)> setter, | ||
| 64 | int default_value | ||
| 65 | ); | ||
| 66 | |||
| 67 | /// Binds a floating-point INI key to a callback. See bind_int for the invocation contract. | ||
| 68 | void bind_float( | ||
| 69 | std::string_view section, | ||
| 70 | std::string_view key, | ||
| 71 | std::string_view display_name, | ||
| 72 | std::function<void(float)> setter, | ||
| 73 | float default_value | ||
| 74 | ); | ||
| 75 | |||
| 76 | /// Binds a boolean INI key to a callback. See bind_int for the invocation contract. | ||
| 77 | void bind_bool( | ||
| 78 | std::string_view section, | ||
| 79 | std::string_view key, | ||
| 80 | std::string_view display_name, | ||
| 81 | std::function<void(bool)> setter, | ||
| 82 | bool default_value | ||
| 83 | ); | ||
| 84 | |||
| 85 | /** | ||
| 86 | * @brief Binds a string INI key to a callback. | ||
| 87 | * @details The setter receives the raw INI value as narrow bytes, with no decoding. The string_view is valid | ||
| 88 | * only during the call and is not guaranteed to be NUL-terminated. The invocation contract matches | ||
| 89 | * bind_int. | ||
| 90 | * @note Setup/control-plane only: registration may allocate and updates the config registry. | ||
| 91 | */ | ||
| 92 | void bind_string( | ||
| 93 | std::string_view section, | ||
| 94 | std::string_view key, | ||
| 95 | std::string_view display_name, | ||
| 96 | std::function<void(std::string_view)> setter, | ||
| 97 | std::string_view default_value | ||
| 98 | ); | ||
| 99 | |||
| 100 | /** | ||
| 101 | * @brief Binds an INI combo string to a callback that receives the parsed combo list. | ||
| 102 | * @details It registers no input binding. The grammar and the "NONE" or empty opt-out match press_combo. The | ||
| 103 | * invocation contract matches bind_int. | ||
| 104 | * @param display_name Name shown in the typo Warning. | ||
| 105 | * @param default_value Combo string used when the key is absent. | ||
| 106 | * @note Setup/control-plane only: registration may allocate and updates the config registry. | ||
| 107 | */ | ||
| 108 | void bind_combos( | ||
| 109 | std::string_view section, | ||
| 110 | std::string_view key, | ||
| 111 | std::string_view display_name, | ||
| 112 | std::function<void(const input::KeyComboList &)> setter, | ||
| 113 | std::string_view default_value | ||
| 114 | ); | ||
| 115 | |||
| 116 | /** | ||
| 117 | * @brief Binds an INI key to a caller-supplied atomic. | ||
| 118 | * @details The matching bind_<T> stores each value into @p out with std::memory_order_relaxed. @p out must | ||
| 119 | * outlive every later load() and reload(). | ||
| 120 | * @note Setup/control-plane only: registration may allocate and updates the config registry. | ||
| 121 | */ | ||
| 122 | template <BindableScalar T> | ||
| 123 | 16 | void bind( | |
| 124 | std::string_view section, | ||
| 125 | std::string_view key, | ||
| 126 | std::string_view display_name, | ||
| 127 | std::atomic<T> &out, | ||
| 128 | T default_value | ||
| 129 | ) | ||
| 130 | { | ||
| 131 | if constexpr (std::same_as<T, int>) | ||
| 132 | { | ||
| 133 | 11 | bind_int( | |
| 134 | section, | ||
| 135 | key, | ||
| 136 | display_name, | ||
| 137 | 52 | [&out](int v) { out.store(v, std::memory_order_relaxed); }, | |
| 138 | default_value | ||
| 139 | ); | ||
| 140 | } | ||
| 141 | else if constexpr (std::same_as<T, bool>) | ||
| 142 | { | ||
| 143 | 3 | bind_bool( | |
| 144 | section, | ||
| 145 | key, | ||
| 146 | display_name, | ||
| 147 | 12 | [&out](bool v) { out.store(v, std::memory_order_relaxed); }, | |
| 148 | default_value | ||
| 149 | ); | ||
| 150 | } | ||
| 151 | else | ||
| 152 | { | ||
| 153 | 2 | bind_float( | |
| 154 | section, | ||
| 155 | key, | ||
| 156 | display_name, | ||
| 157 | 8 | [&out](float v) { out.store(v, std::memory_order_relaxed); }, | |
| 158 | default_value | ||
| 159 | ); | ||
| 160 | } | ||
| 161 | 16 | } | |
| 162 | |||
| 163 | /** | ||
| 164 | * @brief Binds an INI key to an atomic, with the current value of @p out as the default. | ||
| 165 | * @details Registration reads @p out once, relaxed. Initialize @p out before the call. | ||
| 166 | * @note Setup/control-plane only (see the default-taking overload). | ||
| 167 | */ | ||
| 168 | template <BindableScalar T> | ||
| 169 | 6 | void bind(std::string_view section, std::string_view key, std::string_view display_name, std::atomic<T> &out) | |
| 170 | { | ||
| 171 | 8 | bind<T>(section, key, display_name, out, out.load(std::memory_order_relaxed)); | |
| 172 | 6 | } | |
| 173 | |||
| 174 | /** | ||
| 175 | * @brief Binds an INI key to an atomic uint32 through a caller parse function. | ||
| 176 | * @details The parsed value is stored into @p out with std::memory_order_relaxed. @p out has the lifetime rule | ||
| 177 | * of bind. The invocation contract matches bind_int. | ||
| 178 | * @param parse Pure function from the raw INI string to the stored value. | ||
| 179 | * @param default_value INI string that @p parse receives when the key is absent. | ||
| 180 | * @note Setup/control-plane only: registration may allocate and updates the config registry. | ||
| 181 | */ | ||
| 182 | void bind_parsed( | ||
| 183 | std::string_view section, | ||
| 184 | std::string_view key, | ||
| 185 | std::string_view display_name, | ||
| 186 | std::atomic<std::uint32_t> &out, | ||
| 187 | std::function<std::uint32_t(std::string_view)> parse, | ||
| 188 | std::string_view default_value | ||
| 189 | ); | ||
| 190 | |||
| 191 | /** | ||
| 192 | * @brief Binds a log-level INI key that sets the logger level. | ||
| 193 | * @details The logger's string-to-level mapping applies the value at registration and on each load() or | ||
| 194 | * reload(). An unrecognized value falls back to Info. | ||
| 195 | * @param default_value Level string used when the key is absent, for example "INFO" or "DEBUG". | ||
| 196 | * @note Setup/control-plane only: registration may allocate and updates the config registry. | ||
| 197 | */ | ||
| 198 | void bind_log_level(std::string_view section, std::string_view key, std::string_view default_value = "INFO"); | ||
| 199 | |||
| 200 | /** | ||
| 201 | * @brief Binds an INI combo string to a press-mode input binding and returns its guard. | ||
| 202 | * @details It registers a press binding under @p binding_name through input::register_combo. Each load() and | ||
| 203 | * reload() rebinds its keys through input::Input::rebind. Commas separate combos under OR logic, | ||
| 204 | * and '+' separates modifiers from the trailing trigger. A token is a key name or a hex VK code. | ||
| 205 | * | ||
| 206 | * An empty value or "NONE" (case-insensitive, whole trimmed value only) leaves the binding registered | ||
| 207 | * but unbound, with no log record. If no combo of another value parses, one Warning names @p log_name | ||
| 208 | * and the value. | ||
| 209 | * @param log_name Name shown in the typo Warning and in the registration failure record. | ||
| 210 | * @param binding_name Unique input binding name. | ||
| 211 | * @param on_press Callback fired on the key-down edge. | ||
| 212 | * @param default_combo Combo string used when the key is absent. | ||
| 213 | * @param consume If set, registers consume_flag on the key "<ini_key>.Consume" with this default. | ||
| 214 | * @return A guard that owns the binding. Store it, for example in an input::Scope. A dropped guard disables | ||
| 215 | * the binding. If input::register_combo fails, the guard is inert with an empty name(), and the failure | ||
| 216 | * is logged. | ||
| 217 | * @note Setup/control-plane only: the bind registers an input binding and updates the config registry. | ||
| 218 | */ | ||
| 219 | [[nodiscard]] input::BindingGuard press_combo( | ||
| 220 | std::string_view section, | ||
| 221 | std::string_view ini_key, | ||
| 222 | std::string_view log_name, | ||
| 223 | std::string_view binding_name, | ||
| 224 | std::function<void()> on_press, | ||
| 225 | std::string_view default_combo, | ||
| 226 | std::optional<bool> consume = std::nullopt | ||
| 227 | ); | ||
| 228 | |||
| 229 | /** | ||
| 230 | * @brief Binds an INI combo string to a hold-mode input binding and returns its guard. | ||
| 231 | * @details @p on_state_change receives true on the press edge and false on the release edge. Every other | ||
| 232 | * parameter and behavior matches press_combo. | ||
| 233 | * @return A guard that owns the binding. If the guard cancels a held binding, it synthesizes one final | ||
| 234 | * on_state_change(false). Destroy it only in setup or control-plane code. | ||
| 235 | * @note Setup/control-plane only: the bind registers an input binding and updates the config registry. | ||
| 236 | */ | ||
| 237 | [[nodiscard]] input::BindingGuard hold_combo( | ||
| 238 | std::string_view section, | ||
| 239 | std::string_view ini_key, | ||
| 240 | std::string_view log_name, | ||
| 241 | std::string_view binding_name, | ||
| 242 | std::function<void(bool)> on_state_change, | ||
| 243 | std::string_view default_combo, | ||
| 244 | std::optional<bool> consume = std::nullopt | ||
| 245 | ); | ||
| 246 | |||
| 247 | /** | ||
| 248 | * @brief Binds a boolean INI key that sets input suppression for a registered binding. | ||
| 249 | * @details The value sets input::ComboBinding::consume for @p binding_name. Register the binding first, because | ||
| 250 | * each application to an unknown name is a no-op. The invocation contract matches bind_int. | ||
| 251 | * @param default_value Suppression state when the key is absent. | ||
| 252 | * @note Setup/control-plane only: registration may allocate and updates the config registry. | ||
| 253 | */ | ||
| 254 | void consume_flag( | ||
| 255 | std::string_view section, | ||
| 256 | std::string_view ini_key, | ||
| 257 | std::string_view display_name, | ||
| 258 | std::string_view binding_name, | ||
| 259 | bool default_value = false | ||
| 260 | ); | ||
| 261 | |||
| 262 | /** | ||
| 263 | * @brief Registers a hotkey combo that triggers reload() on press. | ||
| 264 | * @details The binding is a press_combo, so the INI value follows the press_combo grammar, opt-out, and typo | ||
| 265 | * Warning. An opt-out @p default_combo instead fails the call, as @return states. | ||
| 266 | * The reload and its setters run on the reload-servicer thread, not on the input poll thread. A repeat | ||
| 267 | * call for the same @p ini_key updates that binding in place, so the last @p default_combo wins. | ||
| 268 | * @param ini_key Key in the [Input] section that holds the combo string. | ||
| 269 | * @param default_combo Combo used when the key is absent, for example "Ctrl+F5". | ||
| 270 | * @return true if the binding was registered, or updated for a repeated @p ini_key. false if @p default_combo | ||
| 271 | * is empty, NONE, or has no parsable combo. false also when an unload latch is set or the input | ||
| 272 | * registration or update fails. A false return keeps any earlier binding for @p ini_key. | ||
| 273 | * @note Setup/control-plane only: the bind registers an input binding and updates the config registry. | ||
| 274 | */ | ||
| 275 | [[nodiscard]] bool reload_hotkey(std::string_view ini_key, std::string_view default_combo); | ||
| 276 | |||
| 277 | /** | ||
| 278 | * @brief Loads all bound settings from the named INI file. | ||
| 279 | * @details reload() reuses this path. If auto-reload watches a different file, the watcher moves to the new | ||
| 280 | * file and keeps its debounce and on_reload callback. On the watcher thread, load() skips that move | ||
| 281 | * and logs an Error record. Call load() from another thread to move the watcher. | ||
| 282 | * @param ini_filename The UTF-8 INI filename, resolved relative to filesystem::get_runtime_directory(). | ||
| 283 | * Ill-formed UTF-8 or an embedded NUL loads the defaults with an Error record. | ||
| 284 | * @note Setup/control-plane only: the load reads the file and runs every bound setter. | ||
| 285 | */ | ||
| 286 | void load(std::string_view ini_filename); | ||
| 287 | |||
| 288 | /** | ||
| 289 | * @brief Re-reads the file of the most recent load() and re-applies every bound setter. | ||
| 290 | * @details The setters are skipped if the file bytes are unchanged since the last fully applied pass and no | ||
| 291 | * bind_* call registered since then. A later pass that fails to read, parse, or apply every setter | ||
| 292 | * cancels that skip. The setters are also skipped if the file is unreadable or fails to parse. A skip | ||
| 293 | * keeps the last-applied values instead of the defaults. | ||
| 294 | * @return false if no load() path is cached, as before any load() or after clear(), or for a refused call from | ||
| 295 | * a bound setter. Otherwise true, also for a skip. | ||
| 296 | * @note Safe from any thread. Concurrent load() and reload() passes are serialized end to end, so a stale pass | ||
| 297 | * never overwrites a fresher one. Only C++ exceptions from setters are caught. A structured exception or | ||
| 298 | * a throw from a noexcept setter is not recoverable. The file header lists the refused setter calls. | ||
| 299 | * @note Setup/control-plane only: the reload reads the file and runs every bound setter. | ||
| 300 | */ | ||
| 301 | [[nodiscard]] bool reload(); | ||
| 302 | |||
| 303 | /** | ||
| 304 | * @brief Starts a background watcher that calls reload() when the INI file changes. | ||
| 305 | * @details The watcher observes the directory of the last load() path. It reloads once @p debounce passes with | ||
| 306 | * no further change, so a burst of saves merges into one reload. The reload and @p on_reload run on | ||
| 307 | * the watcher thread. | ||
| 308 | * @param on_reload Optional. Called after each reload attempt with true if at least one bound setter ran, and | ||
| 309 | * false otherwise. It is not called once an unload latch is set. | ||
| 310 | * @note Setup/control-plane only: the start creates the watcher thread. | ||
| 311 | */ | ||
| 312 | [[nodiscard]] AutoReloadStatus enable_auto_reload( | ||
| 313 | std::chrono::milliseconds debounce = std::chrono::milliseconds{250}, | ||
| 314 | std::function<void(bool)> on_reload = {} | ||
| 315 | ); | ||
| 316 | |||
| 317 | /** | ||
| 318 | * @brief Stops the auto-reload watcher synchronously. | ||
| 319 | * @details Idempotent. On the authorized path it returns after any pending debounced reload callback runs and | ||
| 320 | * the watcher thread exits. If an unload phase is published or the fail-closed loader-lock probe | ||
| 321 | * vetoes, it detaches the watcher thread and does not wait for it. On the watcher thread, for example | ||
| 322 | * inside on_reload, it logs and does not stop the watcher. | ||
| 323 | * @warning The authorized path has no time bound and never detaches a callback in progress. This call waits for | ||
| 324 | * as long as the callback blocks. | ||
| 325 | * @note Setup/control-plane only: the stop joins the watcher thread on the authorized path. | ||
| 326 | */ | ||
| 327 | void disable_auto_reload() noexcept; | ||
| 328 | |||
| 329 | /// Logs the current value of every bound setting, grouped by section. | ||
| 330 | void log_all(); | ||
| 331 | |||
| 332 | /** | ||
| 333 | * @brief Clears every bound setting and the cached load path. | ||
| 334 | * @details It does not stop the auto-reload watcher. Call disable_auto_reload() first, so that no watcher | ||
| 335 | * callback fires against the cleared registry. It releases every reload_hotkey() binding and stops the | ||
| 336 | * reload-servicer thread, so it can wait for a hotkey reload in progress. | ||
| 337 | * @note Setup/control-plane only: the clear tears down the config registry. | ||
| 338 | */ | ||
| 339 | void clear() noexcept; | ||
| 340 | |||
| 341 | class Ini; | ||
| 342 | |||
| 343 | /** @brief A copyable view that forwards each bind call to the matching free function with one section name. */ | ||
| 344 | class SectionBinder | ||
| 345 | { | ||
| 346 | public: | ||
| 347 | /// Constructs a binder scoped to @p section. Prefer Ini::section() / config::section(). | ||
| 348 | explicit SectionBinder(std::string_view section) : m_section(section) {} | ||
| 349 | |||
| 350 | /// Section-scoped atomic bind. See config::bind. | ||
| 351 | template <BindableScalar T> void bind(std::string_view key, std::atomic<T> &out, T default_value) const | ||
| 352 | { | ||
| 353 | config::bind<T>(m_section, key, key, out, default_value); | ||
| 354 | } | ||
| 355 | |||
| 356 | /// Section-scoped atomic bind with the atomic's current value as the default. | ||
| 357 | template <BindableScalar T> void bind(std::string_view key, std::atomic<T> &out) const | ||
| 358 | { | ||
| 359 | config::bind<T>(m_section, key, key, out); | ||
| 360 | } | ||
| 361 | |||
| 362 | /// Section-scoped atomic bind with an explicit display name. | ||
| 363 | template <BindableScalar T> | ||
| 364 | void bind(std::string_view key, std::string_view display_name, std::atomic<T> &out, T default_value) const | ||
| 365 | { | ||
| 366 | config::bind<T>(m_section, key, display_name, out, default_value); | ||
| 367 | } | ||
| 368 | |||
| 369 | /// Section-scoped integer callback bind. See config::bind_int. | ||
| 370 | void bind_int( | ||
| 371 | std::string_view key, | ||
| 372 | std::string_view display_name, | ||
| 373 | std::function<void(int)> setter, | ||
| 374 | int default_value | ||
| 375 | ) const | ||
| 376 | { | ||
| 377 | config::bind_int(m_section, key, display_name, std::move(setter), default_value); | ||
| 378 | } | ||
| 379 | |||
| 380 | /// Section-scoped float callback bind. See config::bind_float. | ||
| 381 | void bind_float( | ||
| 382 | std::string_view key, | ||
| 383 | std::string_view display_name, | ||
| 384 | std::function<void(float)> setter, | ||
| 385 | float default_value | ||
| 386 | ) const | ||
| 387 | { | ||
| 388 | config::bind_float(m_section, key, display_name, std::move(setter), default_value); | ||
| 389 | } | ||
| 390 | |||
| 391 | /// Section-scoped bool callback bind. See config::bind_bool. | ||
| 392 | void bind_bool( | ||
| 393 | std::string_view key, | ||
| 394 | std::string_view display_name, | ||
| 395 | std::function<void(bool)> setter, | ||
| 396 | bool default_value | ||
| 397 | ) const | ||
| 398 | { | ||
| 399 | config::bind_bool(m_section, key, display_name, std::move(setter), default_value); | ||
| 400 | } | ||
| 401 | |||
| 402 | /// Section-scoped string callback bind. See config::bind_string. | ||
| 403 | void bind_string( | ||
| 404 | std::string_view key, | ||
| 405 | std::string_view display_name, | ||
| 406 | std::function<void(std::string_view)> setter, | ||
| 407 | std::string_view default_value | ||
| 408 | ) const | ||
| 409 | { | ||
| 410 | config::bind_string(m_section, key, display_name, std::move(setter), default_value); | ||
| 411 | } | ||
| 412 | |||
| 413 | /// Section-scoped combo-list bind (no input binding). See config::bind_combos. | ||
| 414 | void bind_combos( | ||
| 415 | std::string_view key, | ||
| 416 | std::string_view display_name, | ||
| 417 | std::function<void(const input::KeyComboList &)> setter, | ||
| 418 | std::string_view default_value | ||
| 419 | ) const | ||
| 420 | { | ||
| 421 | config::bind_combos(m_section, key, display_name, std::move(setter), default_value); | ||
| 422 | } | ||
| 423 | |||
| 424 | /// Section-scoped parsed atomic-uint32 bind. See config::bind_parsed. | ||
| 425 | void bind_parsed( | ||
| 426 | std::string_view key, | ||
| 427 | std::string_view display_name, | ||
| 428 | std::atomic<std::uint32_t> &out, | ||
| 429 | std::function<std::uint32_t(std::string_view)> parse, | ||
| 430 | std::string_view default_value | ||
| 431 | ) const | ||
| 432 | { | ||
| 433 | config::bind_parsed(m_section, key, display_name, out, std::move(parse), default_value); | ||
| 434 | } | ||
| 435 | |||
| 436 | /// Section-scoped log-level bind. See config::bind_log_level. | ||
| 437 | void bind_log_level(std::string_view key, std::string_view default_value = "INFO") const | ||
| 438 | { | ||
| 439 | config::bind_log_level(m_section, key, default_value); | ||
| 440 | } | ||
| 441 | |||
| 442 | /// Section-scoped press_combo. See config::press_combo. | ||
| 443 | [[nodiscard]] input::BindingGuard press_combo( | ||
| 444 | std::string_view ini_key, | ||
| 445 | std::string_view log_name, | ||
| 446 | std::string_view binding_name, | ||
| 447 | std::function<void()> on_press, | ||
| 448 | std::string_view default_combo, | ||
| 449 | std::optional<bool> consume = std::nullopt | ||
| 450 | ) const | ||
| 451 | { | ||
| 452 | return config::press_combo( | ||
| 453 | m_section, | ||
| 454 | ini_key, | ||
| 455 | log_name, | ||
| 456 | binding_name, | ||
| 457 | std::move(on_press), | ||
| 458 | default_combo, | ||
| 459 | consume | ||
| 460 | ); | ||
| 461 | } | ||
| 462 | |||
| 463 | /// Section-scoped hold_combo. See config::hold_combo. | ||
| 464 | [[nodiscard]] input::BindingGuard hold_combo( | ||
| 465 | std::string_view ini_key, | ||
| 466 | std::string_view log_name, | ||
| 467 | std::string_view binding_name, | ||
| 468 | std::function<void(bool)> on_state_change, | ||
| 469 | std::string_view default_combo, | ||
| 470 | std::optional<bool> consume = std::nullopt | ||
| 471 | ) const | ||
| 472 | { | ||
| 473 | return config::hold_combo( | ||
| 474 | m_section, | ||
| 475 | ini_key, | ||
| 476 | log_name, | ||
| 477 | binding_name, | ||
| 478 | std::move(on_state_change), | ||
| 479 | default_combo, | ||
| 480 | consume | ||
| 481 | ); | ||
| 482 | } | ||
| 483 | |||
| 484 | /// Section-scoped consume_flag. See config::consume_flag. | ||
| 485 | void consume_flag( | ||
| 486 | std::string_view ini_key, | ||
| 487 | std::string_view display_name, | ||
| 488 | std::string_view binding_name, | ||
| 489 | bool default_value = false | ||
| 490 | ) const | ||
| 491 | { | ||
| 492 | config::consume_flag(m_section, ini_key, display_name, binding_name, default_value); | ||
| 493 | } | ||
| 494 | |||
| 495 | private: | ||
| 496 | std::string m_section; | ||
| 497 | }; | ||
| 498 | |||
| 499 | /// Returns a section-scoped binder for @p name. Equivalent to Ini{}.section(name). | ||
| 500 | [[nodiscard]] inline SectionBinder section(std::string_view name) | ||
| 501 | { | ||
| 502 | return SectionBinder{name}; | ||
| 503 | } | ||
| 504 | |||
| 505 | /** @brief A copyable handle to the one process registry that every Ini and every free function share. */ | ||
| 506 | class Ini | ||
| 507 | { | ||
| 508 | public: | ||
| 509 | Ini() = default; | ||
| 510 | |||
| 511 | /// Returns a section-scoped binder. See config::section. | ||
| 512 | [[nodiscard]] SectionBinder section(std::string_view name) const { return SectionBinder{name}; } | ||
| 513 | |||
| 514 | /// Atomic bind. See config::bind. | ||
| 515 | template <BindableScalar T> | ||
| 516 | 8 | void bind( | |
| 517 | std::string_view sec, | ||
| 518 | std::string_view key, | ||
| 519 | std::string_view display_name, | ||
| 520 | std::atomic<T> &out, | ||
| 521 | T default_value | ||
| 522 | ) const | ||
| 523 | { | ||
| 524 | 8 | config::bind<T>(sec, key, display_name, out, default_value); | |
| 525 | 8 | } | |
| 526 | |||
| 527 | /// Atomic bind with the atomic's current value as the default. See config::bind. | ||
| 528 | template <BindableScalar T> | ||
| 529 | void | ||
| 530 | bind(std::string_view sec, std::string_view key, std::string_view display_name, std::atomic<T> &out) const | ||
| 531 | { | ||
| 532 | config::bind<T>(sec, key, display_name, out); | ||
| 533 | } | ||
| 534 | |||
| 535 | /// Parsed atomic-uint32 bind. See config::bind_parsed. | ||
| 536 | void bind_parsed( | ||
| 537 | std::string_view sec, | ||
| 538 | std::string_view key, | ||
| 539 | std::string_view display_name, | ||
| 540 | std::atomic<std::uint32_t> &out, | ||
| 541 | std::function<std::uint32_t(std::string_view)> parse, | ||
| 542 | std::string_view default_value | ||
| 543 | ) const | ||
| 544 | { | ||
| 545 | config::bind_parsed(sec, key, display_name, out, std::move(parse), default_value); | ||
| 546 | } | ||
| 547 | |||
| 548 | /// Log-level bind. See config::bind_log_level. | ||
| 549 | void | ||
| 550 | bind_log_level(std::string_view sec, std::string_view key, std::string_view default_value = "INFO") const | ||
| 551 | { | ||
| 552 | config::bind_log_level(sec, key, default_value); | ||
| 553 | } | ||
| 554 | |||
| 555 | /// Loads the named INI file. See config::load. | ||
| 556 | 29 | void load(std::string_view ini_filename) const { config::load(ini_filename); } | |
| 557 | |||
| 558 | /// Re-applies bound setters. See config::reload. | ||
| 559 | [[nodiscard]] bool reload() const { return config::reload(); } | ||
| 560 | |||
| 561 | /// Starts the auto-reload watcher. See config::enable_auto_reload. | ||
| 562 | 8 | [[nodiscard]] AutoReloadStatus enable_auto_reload( | |
| 563 | std::chrono::milliseconds debounce = std::chrono::milliseconds{250}, | ||
| 564 | std::function<void(bool)> on_reload = {} | ||
| 565 | ) const | ||
| 566 | { | ||
| 567 | 8 | return config::enable_auto_reload(debounce, std::move(on_reload)); | |
| 568 | } | ||
| 569 | |||
| 570 | /// Stops the auto-reload watcher. See config::disable_auto_reload. | ||
| 571 | void disable_auto_reload() const noexcept { config::disable_auto_reload(); } | ||
| 572 | |||
| 573 | /// Logs every bound setting. See config::log_all. | ||
| 574 | void log_all() const { config::log_all(); } | ||
| 575 | |||
| 576 | /// Clears every bound setting. See config::clear. | ||
| 577 | void clear() const noexcept { config::clear(); } | ||
| 578 | }; | ||
| 579 | } // namespace config | ||
| 580 | } // namespace DetourModKit | ||
| 581 | |||
| 582 | #endif // DETOURMODKIT_CONFIG_HPP | ||
| 583 |