setting events & restart stuff

This commit is contained in:
HJfod 2024-08-31 21:36:53 +03:00
parent 659c168a14
commit 6eb079735f
11 changed files with 185 additions and 25 deletions

View file

@ -9,6 +9,10 @@ namespace geode {
class Impl;
std::unique_ptr<Impl> m_impl;
friend class ::geode::SettingV3;
void markRestartRequired();
public:
static ModSettingsManager* from(Mod* mod);
@ -42,5 +46,11 @@ namespace geode {
std::shared_ptr<SettingV3> get(std::string_view key);
std::shared_ptr<SettingValue> getLegacy(std::string_view key);
std::optional<Setting> getLegacyDefinition(std::string_view key);
/**
* Returns true if any setting with the `"restart-required"` attribute
* has been altered
*/
bool restartRequired() const;
};
}

View file

@ -4,18 +4,18 @@
#include "Loader.hpp"
#include "Setting.hpp"
#include "Mod.hpp"
#include "SettingV3.hpp"
#include <optional>
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<SettingChangedEvent> {
class GEODE_DLL [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] SettingChangedFilter : public EventFilter<SettingChangedEvent> {
protected:
std::string m_modID;
std::optional<std::string> m_targetKey;
@ -40,7 +40,7 @@ namespace geode {
* Listen for built-in setting changes
*/
template<class T>
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 <class T>
std::monostate listenForSettingChanges(
std::string const& settingKey, void (*callback)(T)
) {
(void)new EventListener(
callback, GeodeSettingChangedFilter<T>(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();
}
}

View file

@ -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<SettingV3> {
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<SettingV3> getSetting() const;
};
class GEODE_DLL SettingChangedEventV3 final : public Event {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
public:
SettingChangedEventV3(std::shared_ptr<SettingV3> setting);
std::shared_ptr<SettingV3> getSetting() const;
};
class GEODE_DLL SettingChangedFilterV3 final : public EventFilter<SettingChangedEventV3> {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
public:
using Callback = void(std::shared_ptr<SettingV3>);
ListenerResult handle(utils::MiniFunction<Callback> 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<std::string> const& settingKey
);
SettingChangedFilterV3(Mod* mod, std::optional<std::string> const& settingKey);
SettingChangedFilterV3(SettingChangedFilterV3 const&);
};
class GEODE_DLL SettingNodeSizeChangeEventV3 : public Event {
private:
class Impl;
@ -512,4 +556,25 @@ namespace geode {
struct SettingTypeForValueType<cocos2d::ccColor4B> {
using SettingType = Color4BSettingV3;
};
template <class T>
EventListener<SettingChangedFilterV3>* listenForSettingChanges(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) {
using Ty = typename SettingTypeForValueType<T>::SettingType;
return new EventListener(
[callback = std::move(callback)](std::shared_ptr<SettingV3> setting) {
if (auto ty = typeinfo_pointer_cast<Ty>(setting)) {
callback(ty->getValue());
}
},
SettingChangedFilterV3(mod, std::string(settingKey))
);
}
EventListener<SettingChangedFilterV3>* listenForSettingChanges(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) {
using T = std::remove_cvref_t<utils::function::Arg<0, decltype(callback)>>;
return listenForSettingChanges<T>(settingKey, std::move(callback), mod);
}
GEODE_DLL EventListener<SettingChangedFilterV3>* listenForAllSettingChanges(
std::function<void(std::shared_ptr<SettingV3>)> const& callback,
Mod* mod = getMod()
);
}

View file

@ -17,13 +17,22 @@
namespace geode {
using ByteVector = std::vector<uint8_t>;
// todo in v4: remove this
template <typename T>
[[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 <typename T>
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

View file

@ -78,6 +78,7 @@ public:
};
std::string modID;
std::unordered_map<std::string, SettingInfo> 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<Setting> ModSettingsManager::getLegacyDefinition(std::string_view
}
return std::nullopt;
}
bool ModSettingsManager::restartRequired() const {
return m_impl->restartRequired;
}

View file

@ -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();
}
}
}

View file

@ -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");

View file

@ -1,4 +1,6 @@
#include <Geode/loader/SettingV3.hpp>
#include <Geode/loader/SettingEvent.hpp>
#include <Geode/loader/ModSettingsManager.hpp>
#include <Geode/utils/JsonValidation.hpp>
#include <regex>
#include "SettingNodeV3.hpp"
@ -397,6 +399,63 @@ namespace enable_if_parsing {
};
}
class SettingChangedEventV3::Impl final {
public:
std::shared_ptr<SettingV3> setting;
};
SettingChangedEventV3::SettingChangedEventV3(std::shared_ptr<SettingV3> setting)
: m_impl(std::make_shared<Impl>())
{
m_impl->setting = setting;
}
std::shared_ptr<SettingV3> SettingChangedEventV3::getSetting() const {
return m_impl->setting;
}
class SettingChangedFilterV3::Impl final {
public:
std::string modID;
std::optional<std::string> settingKey;
};
ListenerResult SettingChangedFilterV3::handle(utils::MiniFunction<Callback> 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<std::string> const& settingKey
) : m_impl(std::make_shared<Impl>())
{
m_impl->modID = modID;
m_impl->settingKey = settingKey;
}
SettingChangedFilterV3::SettingChangedFilterV3(Mod* mod, std::optional<std::string> const& settingKey)
: SettingChangedFilterV3(mod->getID(), settingKey) {}
SettingChangedFilterV3::SettingChangedFilterV3(SettingChangedFilterV3 const&) = default;
EventListener<SettingChangedFilterV3>* geode::listenForAllSettingChanges(
std::function<void(std::shared_ptr<SettingV3>)> const& callback,
Mod* mod
) {
return new EventListener(
[callback](std::shared_ptr<SettingV3> 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<Setting> SettingV3::convertToLegacy() const {
return std::nullopt;
}

View file

@ -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;

View file

@ -12,6 +12,7 @@
#include "sources/ModListSource.hpp"
#include "UpdateModListState.hpp"
#include <server/DownloadManager.hpp>
#include <Geode/loader/SettingV3.hpp>
using namespace geode::prelude;
@ -39,6 +40,7 @@ protected:
EventListener<UpdateModListStateFilter> m_updateStateListener;
EventListener<server::ModDownloadFilter> m_downloadListener;
DownloadState m_lastState = DownloadState::None;
EventListener<EventFilter<SettingNodeValueChangeEventV3>> m_settingNodeListener;
bool init();
void updateState();

View file

@ -1,5 +1,6 @@
#include "ModListSource.hpp"
#include <server/DownloadManager.hpp>
#include <Geode/loader/ModSettingsManager.hpp>
#define FTS_FUZZY_MATCH_IMPLEMENTATION
#include <Geode/external/fts/fts_fuzzy_match.h>
@ -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;