diff --git a/loader/include/Geode/loader/ModSettingsManager.hpp b/loader/include/Geode/loader/ModSettingsManager.hpp index 36b8ac34..6281bf7b 100644 --- a/loader/include/Geode/loader/ModSettingsManager.hpp +++ b/loader/include/Geode/loader/ModSettingsManager.hpp @@ -9,6 +9,10 @@ namespace geode { class Impl; std::unique_ptr m_impl; + friend class ::geode::SettingV3; + + void markRestartRequired(); + public: static ModSettingsManager* from(Mod* mod); @@ -42,5 +46,11 @@ namespace geode { std::shared_ptr get(std::string_view key); std::shared_ptr getLegacy(std::string_view key); std::optional getLegacyDefinition(std::string_view key); + + /** + * Returns true if any setting with the `"restart-required"` attribute + * has been altered + */ + bool restartRequired() const; }; } diff --git a/loader/include/Geode/loader/SettingEvent.hpp b/loader/include/Geode/loader/SettingEvent.hpp index 7ad33fe1..b84b7884 100644 --- a/loader/include/Geode/loader/SettingEvent.hpp +++ b/loader/include/Geode/loader/SettingEvent.hpp @@ -4,18 +4,18 @@ #include "Loader.hpp" #include "Setting.hpp" #include "Mod.hpp" - +#include "SettingV3.hpp" #include namespace geode { - struct GEODE_DLL SettingChangedEvent : public Event { + struct GEODE_DLL [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] SettingChangedEvent : public Event { Mod* mod; SettingValue* value; SettingChangedEvent(Mod* mod, SettingValue* value); }; - class GEODE_DLL SettingChangedFilter : public EventFilter { + class GEODE_DLL [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] SettingChangedFilter : public EventFilter { protected: std::string m_modID; std::optional m_targetKey; @@ -40,7 +40,7 @@ namespace geode { * Listen for built-in setting changes */ template - class GeodeSettingChangedFilter : public SettingChangedFilter { + class [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] GeodeSettingChangedFilter : public SettingChangedFilter { public: using Callback = void(T); @@ -60,21 +60,4 @@ namespace geode { ) : SettingChangedFilter(modID, settingID) {} GeodeSettingChangedFilter(GeodeSettingChangedFilter const&) = default; }; - - template - std::monostate listenForSettingChanges( - std::string const& settingKey, void (*callback)(T) - ) { - (void)new EventListener( - callback, GeodeSettingChangedFilter(getMod()->getID(), settingKey) - ); - return std::monostate(); - } - - static std::monostate listenForAllSettingChanges(void (*callback)(SettingValue*)) { - (void)new EventListener( - callback, SettingChangedFilter(getMod()->getID(), std::nullopt) - ); - return std::monostate(); - } } diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index fa9e1c7a..9d51732d 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -8,6 +8,7 @@ #include "../utils/cocos.hpp" // this unfortunately has to be included because of C++ templates #include "../utils/JsonValidation.hpp" +#include "../utils/function.hpp" // todo in v4: this can be removed as well as the friend decl in LegacyCustomSettingV3 class LegacyCustomSettingToV3Node; @@ -16,6 +17,8 @@ class ModSettingsPopup; namespace geode { class ModSettingsManager; class SettingNodeV3; + // todo in v4: remove this + class SettingValue; class GEODE_DLL SettingV3 : public std::enable_shared_from_this { private: @@ -27,6 +30,14 @@ namespace geode { Result<> parseSharedProperties(std::string const& key, std::string const& modID, matjson::Value const& value, bool onlyNameAndDesc = false); void parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc = false); + /** + * Mark that the value of this setting has changed. This should be + * ALWAYS called on every setter that can modify the setting's state! + */ + void markChanged(); + + friend class ::geode::SettingValue; + public: SettingV3(); virtual ~SettingV3(); @@ -120,6 +131,7 @@ namespace geode { } void setValue(V value) { this->getValueMut() = this->isValid(value) ? value : this->getDefaultValue(); + this->markChanged(); } virtual Result<> isValid(V value) const = 0; @@ -452,6 +464,38 @@ namespace geode { std::shared_ptr getSetting() const; }; + class GEODE_DLL SettingChangedEventV3 final : public Event { + private: + class Impl; + std::shared_ptr m_impl; + + public: + SettingChangedEventV3(std::shared_ptr setting); + + std::shared_ptr getSetting() const; + }; + class GEODE_DLL SettingChangedFilterV3 final : public EventFilter { + private: + class Impl; + std::shared_ptr m_impl; + + public: + using Callback = void(std::shared_ptr); + + ListenerResult handle(utils::MiniFunction fn, SettingChangedEventV3* event); + /** + * Listen to changes on a setting, or all settings + * @param modID Mod whose settings to listen to + * @param settingKey Setting to listen to, or all settings if nullopt + */ + SettingChangedFilterV3( + std::string const& modID, + std::optional const& settingKey + ); + SettingChangedFilterV3(Mod* mod, std::optional const& settingKey); + SettingChangedFilterV3(SettingChangedFilterV3 const&); + }; + class GEODE_DLL SettingNodeSizeChangeEventV3 : public Event { private: class Impl; @@ -512,4 +556,25 @@ namespace geode { struct SettingTypeForValueType { using SettingType = Color4BSettingV3; }; + + template + EventListener* listenForSettingChanges(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) { + using Ty = typename SettingTypeForValueType::SettingType; + return new EventListener( + [callback = std::move(callback)](std::shared_ptr setting) { + if (auto ty = typeinfo_pointer_cast(setting)) { + callback(ty->getValue()); + } + }, + SettingChangedFilterV3(mod, std::string(settingKey)) + ); + } + EventListener* listenForSettingChanges(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) { + using T = std::remove_cvref_t>; + return listenForSettingChanges(settingKey, std::move(callback), mod); + } + GEODE_DLL EventListener* listenForAllSettingChanges( + std::function)> const& callback, + Mod* mod = getMod() + ); } diff --git a/loader/include/Geode/utils/general.hpp b/loader/include/Geode/utils/general.hpp index d23b3c63..c174658c 100644 --- a/loader/include/Geode/utils/general.hpp +++ b/loader/include/Geode/utils/general.hpp @@ -17,13 +17,22 @@ namespace geode { using ByteVector = std::vector; + // todo in v4: remove this template + [[deprecated("Use geode::toBytes instead")]] ByteVector toByteArray(T const& a) { ByteVector out; out.resize(sizeof(T)); std::memcpy(out.data(), &a, sizeof(T)); return out; } + template + ByteVector toBytes(T const& a) { + ByteVector out; + out.resize(sizeof(T)); + std::memcpy(out.data(), &a, sizeof(T)); + return out; + } namespace utils { // helper for std::visit diff --git a/loader/src/loader/ModSettingsManager.cpp b/loader/src/loader/ModSettingsManager.cpp index dc625ac7..e01d1887 100644 --- a/loader/src/loader/ModSettingsManager.cpp +++ b/loader/src/loader/ModSettingsManager.cpp @@ -78,6 +78,7 @@ public: }; std::string modID; std::unordered_map settings; + bool restartRequired = false; void createSettings() { for (auto& [key, setting] : settings) { @@ -103,6 +104,7 @@ public: }; ModSettingsManager* ModSettingsManager::from(Mod* mod) { + if (!mod) return nullptr; return ModImpl::getImpl(mod)->m_settings.get(); } @@ -136,6 +138,10 @@ ModSettingsManager::ModSettingsManager(ModMetadata const& metadata) ModSettingsManager::~ModSettingsManager() {} ModSettingsManager::ModSettingsManager(ModSettingsManager&&) = default; +void ModSettingsManager::markRestartRequired() { + m_impl->restartRequired = true; +} + Result<> ModSettingsManager::registerCustomSettingType(std::string_view type, SettingGenerator generator) { GEODE_UNWRAP(SharedSettingTypesPool::get().add(m_impl->modID, type, generator)); m_impl->createSettings(); @@ -230,3 +236,7 @@ std::optional ModSettingsManager::getLegacyDefinition(std::string_view } return std::nullopt; } + +bool ModSettingsManager::restartRequired() const { + return m_impl->restartRequired; +} diff --git a/loader/src/loader/Setting.cpp b/loader/src/loader/Setting.cpp index c7892cd3..f9562eb2 100644 --- a/loader/src/loader/Setting.cpp +++ b/loader/src/loader/Setting.cpp @@ -280,10 +280,10 @@ std::string SettingValue::getModID() const { } void SettingValue::valueChanged() { - // this is actually p neat because now if the mod gets disabled this wont - // post the event so that side-effect is automatically handled :3 if (auto mod = Loader::get()->getLoadedMod(m_modID)) { - SettingChangedEvent(mod, this).post(); + if (auto sett = mod->getSettingV3(m_key)) { + sett->markChanged(); + } } } diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 5f8add60..0caee58d 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -125,7 +125,7 @@ void SettingNodeV3::updateState() { m_impl->errorLabel->setString(desc->c_str()); } } - if (m_impl->setting->requiresRestart() && (this->hasUncommittedChanges() || m_impl->committed)) { + if (m_impl->setting->requiresRestart() && m_impl->committed) { m_impl->errorLabel->setVisible(true); m_impl->errorLabel->setColor("mod-list-restart-required-label"_cc3b); m_impl->errorLabel->setString("Restart Required"); diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index 8f63dc97..af2d5a43 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include "SettingNodeV3.hpp" @@ -397,6 +399,63 @@ namespace enable_if_parsing { }; } +class SettingChangedEventV3::Impl final { +public: + std::shared_ptr setting; +}; + +SettingChangedEventV3::SettingChangedEventV3(std::shared_ptr setting) + : m_impl(std::make_shared()) +{ + m_impl->setting = setting; +} + +std::shared_ptr SettingChangedEventV3::getSetting() const { + return m_impl->setting; +} + +class SettingChangedFilterV3::Impl final { +public: + std::string modID; + std::optional settingKey; +}; + +ListenerResult SettingChangedFilterV3::handle(utils::MiniFunction fn, SettingChangedEventV3* event) { + if ( + event->getSetting()->getModID() == m_impl->modID && + !m_impl->settingKey || event->getSetting()->getKey() == m_impl->settingKey + ) { + fn(event->getSetting()); + } + return ListenerResult::Propagate; +} + +SettingChangedFilterV3::SettingChangedFilterV3( + std::string const& modID, + std::optional const& settingKey +) : m_impl(std::make_shared()) +{ + m_impl->modID = modID; + m_impl->settingKey = settingKey; +} + +SettingChangedFilterV3::SettingChangedFilterV3(Mod* mod, std::optional const& settingKey) + : SettingChangedFilterV3(mod->getID(), settingKey) {} + +SettingChangedFilterV3::SettingChangedFilterV3(SettingChangedFilterV3 const&) = default; + +EventListener* geode::listenForAllSettingChanges( + std::function)> const& callback, + Mod* mod +) { + return new EventListener( + [callback](std::shared_ptr setting) { + callback(setting); + }, + SettingChangedFilterV3(mod->getID(), std::nullopt) + ); +} + class SettingV3::GeodeImpl { public: std::string modID; @@ -485,6 +544,19 @@ Mod* SettingV3::getMod() const { return Loader::get()->getInstalledMod(m_impl->modID); } +void SettingV3::markChanged() { + auto manager = ModSettingsManager::from(this->getMod()); + if (m_impl->requiresRestart) { + manager->markRestartRequired(); + } + SettingChangedEventV3(shared_from_this()).post(); + if (manager) { + // Use ModSettingsManager rather than convertToLegacyValue since it + // caches the result and we want to have that for performance + SettingChangedEvent(this->getMod(), manager->getLegacy(this->getKey()).get()).post(); + } +} + std::optional SettingV3::convertToLegacy() const { return std::nullopt; } diff --git a/loader/src/ui/mods/ModsLayer.cpp b/loader/src/ui/mods/ModsLayer.cpp index 1b83dbcd..ce7562f8 100644 --- a/loader/src/ui/mods/ModsLayer.cpp +++ b/loader/src/ui/mods/ModsLayer.cpp @@ -90,6 +90,11 @@ bool ModsStatusNode::init() { m_downloadListener.bind([this](auto) { this->updateState(); }); + m_settingNodeListener.bind([this](SettingNodeValueChangeEventV3* ev) { + this->updateState(); + return ListenerResult::Propagate; + }); + this->updateState(); return true; diff --git a/loader/src/ui/mods/ModsLayer.hpp b/loader/src/ui/mods/ModsLayer.hpp index 7e7de630..35e36b97 100644 --- a/loader/src/ui/mods/ModsLayer.hpp +++ b/loader/src/ui/mods/ModsLayer.hpp @@ -12,6 +12,7 @@ #include "sources/ModListSource.hpp" #include "UpdateModListState.hpp" #include +#include using namespace geode::prelude; @@ -39,6 +40,7 @@ protected: EventListener m_updateStateListener; EventListener m_downloadListener; DownloadState m_lastState = DownloadState::None; + EventListener> m_settingNodeListener; bool init(); void updateState(); diff --git a/loader/src/ui/mods/sources/ModListSource.cpp b/loader/src/ui/mods/sources/ModListSource.cpp index 19ced217..e19e1c43 100644 --- a/loader/src/ui/mods/sources/ModListSource.cpp +++ b/loader/src/ui/mods/sources/ModListSource.cpp @@ -1,5 +1,6 @@ #include "ModListSource.hpp" #include +#include #define FTS_FUZZY_MATCH_IMPLEMENTATION #include @@ -90,6 +91,9 @@ bool ModListSource::isRestartRequired() { if (mod->getRequestedAction() != ModRequestedAction::None) { return true; } + if (ModSettingsManager::from(mod)->restartRequired()) { + return true; + } } if (server::ModDownloadManager::get()->wantsRestart()) { return true;