diff --git a/loader/include/Geode/Loader.hpp b/loader/include/Geode/Loader.hpp index bb4e1789..fbb5e04f 100644 --- a/loader/include/Geode/Loader.hpp +++ b/loader/include/Geode/Loader.hpp @@ -7,3 +7,4 @@ #include "loader/Loader.hpp" #include "loader/Interface.hpp" #include "loader/Setting.hpp" +#include "loader/SettingEvent.hpp" diff --git a/loader/include/Geode/loader/Event.hpp b/loader/include/Geode/loader/Event.hpp index 3362f15b..8e0ec567 100644 --- a/loader/include/Geode/loader/Event.hpp +++ b/loader/include/Geode/loader/Event.hpp @@ -4,33 +4,40 @@ #include <type_traits> #include "Mod.hpp" #include "Interface.hpp" +#include <unordered_set> namespace geode { class Mod; class Event; + enum class PassThrough : bool { + Propagate, + Stop, + }; + struct GEODE_DLL BasicEventHandler { - virtual bool onEvent(Event*) = 0; + virtual PassThrough passThrough(Event*) = 0; void listen(); void unlisten(); }; class GEODE_DLL Event { - static std::vector<BasicEventHandler*> handlers; + static std::unordered_set<BasicEventHandler*> s_handlers; + friend BasicEventHandler; Mod* m_sender; public: - static std::vector<BasicEventHandler*> const& getHandlers(); + static std::unordered_set<BasicEventHandler*> const& getHandlers(); void postFrom(Mod* sender); inline void post() { postFrom(Mod::get()); } - Mod* sender(); + Mod* getSender(); virtual ~Event(); }; @@ -38,12 +45,12 @@ namespace geode { template <typename T> class EventHandler : public BasicEventHandler { public: - virtual bool handle(T*) = 0; - bool onEvent(Event* ev) override { + virtual PassThrough handle(T*) = 0; + PassThrough passThrough(Event* ev) override { if (auto myev = dynamic_cast<T*>(ev)) { return handle(myev); } - return true; + return PassThrough::Propagate; } EventHandler() { diff --git a/loader/include/Geode/loader/Setting.hpp b/loader/include/Geode/loader/Setting.hpp index 338506f1..fec606f9 100644 --- a/loader/include/Geode/loader/Setting.hpp +++ b/loader/include/Geode/loader/Setting.hpp @@ -17,12 +17,15 @@ namespace geode { using ModJson = nlohmann::ordered_json; + class Setting; class SettingNode; class BoolSetting; class IntSetting; class FloatSetting; class StringSetting; + struct ModInfo; + enum class SettingType { Bool, Int, @@ -34,9 +37,21 @@ namespace geode { User, }; - class GEODE_DLL Setting { + /** + * Base class for all settings in Geode mods. Note that for most purposes + * you should use the built-in setting types. If you need a custom setting + * type however, inherit from this class. Do note that you are responsible + * for things like storing the default value, broadcasting value change + * events, making the setting node etc. + */ + class GEODE_DLL Setting : + public std::enable_shared_from_this<Setting> + { protected: std::string m_key; + std::string m_modID; + + friend struct ModInfo; static Result<std::shared_ptr<Setting>> parse( std::string const& type, @@ -59,6 +74,8 @@ namespace geode { virtual SettingNode* createNode(float width) = 0; + void valueChanged(); + std::string getKey() const; virtual SettingType getType() const = 0; }; @@ -161,6 +178,7 @@ namespace geode { if constexpr (std::is_base_of_v<IMatch<Class, ValueType>, Class>) { static_cast<Class*>(this)->constrainMatch(m_value); } + this->valueChanged(); } Result<> isValidValue(ValueType value) { @@ -392,8 +410,7 @@ namespace geode { } class GEODE_DLL BoolSetting : - public GeodeSetting<BoolSetting, bool, SettingType::Bool>, - public std::enable_shared_from_this<BoolSetting> + public GeodeSetting<BoolSetting, bool, SettingType::Bool> { public: SettingNode* createNode(float width) override; @@ -403,7 +420,6 @@ namespace geode { public GeodeSetting<IntSetting, int64_t, SettingType::Int>, public IOneOf<IntSetting, int64_t>, public IMinMax<int64_t>, - public std::enable_shared_from_this<IntSetting>, public ICArrows, public ICSlider<int64_t>, public ICInput { public: @@ -414,7 +430,6 @@ namespace geode { public GeodeSetting<FloatSetting, double, SettingType::Float>, public IOneOf<FloatSetting, double>, public IMinMax<double>, - public std::enable_shared_from_this<FloatSetting>, public ICArrows, public ICSlider<double>, public ICInput { public: @@ -424,8 +439,7 @@ namespace geode { class GEODE_DLL StringSetting : public GeodeSetting<StringSetting, std::string, SettingType::String>, public IOneOf<StringSetting, std::string>, - public IMatch<StringSetting, std::string>, - public std::enable_shared_from_this<StringSetting> + public IMatch<StringSetting, std::string> { public: SettingNode* createNode(float width) override; @@ -433,24 +447,21 @@ namespace geode { class GEODE_DLL FileSetting : public GeodeSetting<FileSetting, ghc::filesystem::path, SettingType::File>, - public ICFileFilters, - public std::enable_shared_from_this<FileSetting> + public ICFileFilters { public: SettingNode* createNode(float width) override; }; class GEODE_DLL ColorSetting : - public GeodeSetting<ColorSetting, cocos2d::ccColor3B, SettingType::Color>, - public std::enable_shared_from_this<ColorSetting> + public GeodeSetting<ColorSetting, cocos2d::ccColor3B, SettingType::Color> { public: SettingNode* createNode(float width) override; }; class GEODE_DLL ColorAlphaSetting : - public GeodeSetting<ColorAlphaSetting, cocos2d::ccColor4B, SettingType::ColorAlpha>, - public std::enable_shared_from_this<ColorAlphaSetting> + public GeodeSetting<ColorAlphaSetting, cocos2d::ccColor4B, SettingType::ColorAlpha> { public: SettingNode* createNode(float width) override; diff --git a/loader/include/Geode/loader/SettingEvent.hpp b/loader/include/Geode/loader/SettingEvent.hpp new file mode 100644 index 00000000..18572664 --- /dev/null +++ b/loader/include/Geode/loader/SettingEvent.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include "Event.hpp" +#include <optional> +#include "Setting.hpp" + +namespace geode { + class GEODE_DLL SettingChangedEvent : public Event { + protected: + std::string m_modID; + std::shared_ptr<Setting> m_setting; + + public: + SettingChangedEvent( + std::string const& modID, + std::shared_ptr<Setting> setting + ); + std::string getModID() const; + std::shared_ptr<Setting> getSetting() const; + }; + + template<class T> + class SettingChangedEventHandler : public EventHandler<SettingChangedEvent> { + public: + using Consumer = void(*)(std::shared_ptr<T>); + + static_assert( + std::is_base_of_v<Setting, T>, + "Setting must inherit from the Setting class" + ); + + protected: + Consumer m_consumer; + std::string m_modID; + std::optional<std::string> m_targetKey; + + public: + PassThrough handle(SettingChangedEvent* event) override { + if ( + m_modID == event->getModID() && ( + !m_targetKey || + m_targetKey.value() == event->getSetting()->getKey() + )) { + m_consumer(std::static_pointer_cast<T>(event->getSetting())); + } + return PassThrough::Propagate; + } + + /** + * Listen to changes on a specific setting + */ + SettingChangedEventHandler( + std::string const& modID, + std::string const& settingID, + Consumer handler + ) : m_modID(modID), + m_targetKey(settingID), + m_consumer(handler) {} + + /** + * Listen to changes on all of a mods' settings + */ + SettingChangedEventHandler( + std::string const& modID, + Consumer handler + ) : m_modID(modID), + m_targetKey(std::nullopt), + m_consumer(handler) {} + }; +} + diff --git a/loader/src/internal/InternalLoader.cpp b/loader/src/internal/InternalLoader.cpp index a8b23a7b..ec279877 100644 --- a/loader/src/internal/InternalLoader.cpp +++ b/loader/src/internal/InternalLoader.cpp @@ -56,12 +56,14 @@ void InternalLoader::executeGDThreadQueue() { m_gdThreadMutex.unlock(); } -void InternalLoader::queueConsoleMessage(LogPtr* msg) { - this->m_logQueue.push_back(msg); +void InternalLoader::logConsoleMessage(LogPtr* msg) { + if (m_platformConsoleOpen) { + std::cout << msg->toString(true); + } } -bool InternalLoader::platformConsoleReady() const { - return m_platformConsoleReady; +bool InternalLoader::platformConsoleOpen() const { + return m_platformConsoleOpen; } bool InternalLoader::shownInfoAlert(std::string const& key) { @@ -85,42 +87,28 @@ void InternalLoader::platformMessageBox(const char* title, std::string const& in MessageBoxA(nullptr, info.c_str(), title, MB_ICONERROR); } -void InternalLoader::setupPlatformConsole() { - if (m_platformConsoleReady) return; +void InternalLoader::openPlatformConsole() { + if (m_platformConsoleOpen) return; if (AllocConsole() == 0) return; // redirect console output freopen_s(reinterpret_cast<FILE**>(stdout), "CONOUT$", "w", stdout); freopen_s(reinterpret_cast<FILE**>(stdin), "CONIN$", "r", stdin); - m_platformConsoleReady = true; -} + m_platformConsoleOpen = true; -void InternalLoader::awaitPlatformConsole() { - if (!m_platformConsoleReady) return; - - for (auto const& log : m_logQueue) { + for (auto const& log : Loader::get()->getLogs()) { std::cout << log->toString(true) << "\n"; - m_logQueue.clear(); } - - std::string inp; - getline(std::cin, inp); - std::string inpa; - std::stringstream ss(inp); - std::vector<std::string> args; - - while (ss >> inpa) args.push_back(inpa); - ss.clear(); - - this->awaitPlatformConsole(); } void InternalLoader::closePlatformConsole() { - if (!m_platformConsoleReady) return; + if (!m_platformConsoleOpen) return; fclose(stdin); fclose(stdout); FreeConsole(); + + m_platformConsoleOpen = false; } #elif defined(GEODE_IS_MACOS) @@ -130,8 +118,8 @@ void InternalLoader::platformMessageBox(const char* title, std::string const& in std::cout << title << ": " << info << std::endl; } -void InternalLoader::setupPlatformConsole() { - m_platformConsoleReady = true; +void InternalLoader::openPlatformConsole() { + m_platformConsoleOpen = true; } void InternalLoader::awaitPlatformConsole() { @@ -150,13 +138,13 @@ void InternalLoader::platformMessageBox(const char* title, std::string const& in std::cout << title << ": " << info << std::endl; } -void InternalLoader::setupPlatformConsole() { +void InternalLoader::openPlatformConsole() { ghc::filesystem::path(getpwuid(getuid())->pw_dir); freopen(ghc::filesystem::path( utils::file::geodeRoot() / "geode_log.txt" ).string().c_str(),"w",stdout); InternalLoader:: - m_platformConsoleReady = true; + m_platformConsoleOpen = true; } void InternalLoader::awaitPlatformConsole() { diff --git a/loader/src/internal/InternalLoader.hpp b/loader/src/internal/InternalLoader.hpp index c817df7c..099707a0 100644 --- a/loader/src/internal/InternalLoader.hpp +++ b/loader/src/internal/InternalLoader.hpp @@ -17,10 +17,9 @@ USE_GEODE_NAMESPACE(); */ class InternalLoader : public Loader { protected: - std::vector<LogPtr*> m_logQueue; std::vector<std::function<void(void)>> m_gdThreadQueue; mutable std::mutex m_gdThreadMutex; - bool m_platformConsoleReady = false; + bool m_platformConsoleOpen = false; std::unordered_set<std::string> m_shownInfoAlerts; void saveInfoAlerts(nlohmann::json& json); @@ -48,10 +47,9 @@ public: void queueInGDThread(std::function<void GEODE_CALL(void)> func); void executeGDThreadQueue(); - void queueConsoleMessage(LogPtr*); - bool platformConsoleReady() const; - void setupPlatformConsole(); - void awaitPlatformConsole(); + void logConsoleMessage(LogPtr*); + bool platformConsoleOpen() const; + void openPlatformConsole(); void closePlatformConsole(); static void platformMessageBox(const char* title, std::string const& info); diff --git a/loader/src/load/Event.cpp b/loader/src/load/Event.cpp index 410dfa3a..73a78f3c 100644 --- a/loader/src/load/Event.cpp +++ b/loader/src/load/Event.cpp @@ -3,15 +3,14 @@ USE_GEODE_NAMESPACE(); -std::vector<BasicEventHandler*> Event::handlers = {}; +std::unordered_set<BasicEventHandler*> Event::s_handlers = {}; void BasicEventHandler::listen() { - if (!utils::vector::contains(Event::handlers, this)) - Event::handlers.push_back(this); + Event::s_handlers.insert(this); } void BasicEventHandler::unlisten() { - utils::vector::erase(Event::handlers, this); + Event::s_handlers.erase(this); } Event::~Event() {} @@ -20,12 +19,17 @@ void Event::postFrom(Mod* m) { if (m) m_sender = m; - for (auto h : Event::handlers) { - if (!h->onEvent(this)) + for (auto h : Event::s_handlers) { + if (h->passThrough(this) == PassThrough::Stop) { break; + } } } -std::vector<BasicEventHandler*> const& Event::getHandlers() { - return Event::handlers; +Mod* Event::getSender() { + return m_sender; +} + +std::unordered_set<BasicEventHandler*> const& Event::getHandlers() { + return Event::s_handlers; } diff --git a/loader/src/load/Loader.cpp b/loader/src/load/Loader.cpp index ea871000..b830eb68 100644 --- a/loader/src/load/Loader.cpp +++ b/loader/src/load/Loader.cpp @@ -376,11 +376,7 @@ Loader::~Loader() { void Loader::pushLog(LogPtr* logptr) { m_logs.push_back(logptr); - if (InternalLoader::get()->platformConsoleReady()) { - std::cout << logptr->toString(true); - } else { - InternalLoader::get()->queueConsoleMessage(logptr); - } + InternalLoader::get()->logConsoleMessage(logptr); m_logStream << logptr->toString(true) << std::endl; } @@ -458,12 +454,7 @@ bool Loader::supportedModVersion(VersionInfo const& version) { } void Loader::openPlatformConsole() { - if (!InternalLoader::get()->platformConsoleReady()) { - InternalLoader::get()->setupPlatformConsole(); - std::thread([]() { - InternalLoader::get()->awaitPlatformConsole(); - }).detach(); - } + InternalLoader::get()->openPlatformConsole(); } void Loader::closePlatfromConsole() { diff --git a/loader/src/load/Log.cpp b/loader/src/load/Log.cpp index 73a6e140..1d0bab95 100644 --- a/loader/src/load/Log.cpp +++ b/loader/src/load/Log.cpp @@ -136,7 +136,7 @@ void Log::flush() { Log::~Log() { this->flush(); - if (InternalLoader::get()->platformConsoleReady()) { + if (InternalLoader::get()->platformConsoleOpen()) { std::cout << std::endl; } } diff --git a/loader/src/load/ModInfo.cpp b/loader/src/load/ModInfo.cpp index 1d528560..bf9b5579 100644 --- a/loader/src/load/ModInfo.cpp +++ b/loader/src/load/ModInfo.cpp @@ -64,9 +64,11 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) { } for (auto& [key, value] : root.has("settings").items()) { - auto sett = Setting::parse(key, value.json()); - PROPAGATE(sett); - info.m_settings.push_back({ key, sett.value() }); + auto settRes = Setting::parse(key, value.json()); + PROPAGATE(settRes); + auto sett = settRes.value(); + sett->m_modID = info.m_id; + info.m_settings.push_back({ key, sett }); } if (auto resources = root.has("resources").obj()) { diff --git a/loader/src/load/Setting.cpp b/loader/src/load/Setting.cpp index b4956b3a..eb094ab2 100644 --- a/loader/src/load/Setting.cpp +++ b/loader/src/load/Setting.cpp @@ -1,4 +1,5 @@ #include <Geode/loader/Setting.hpp> +#include <Geode/loader/SettingEvent.hpp> #include <Geode/utils/general.hpp> #include <Geode/loader/SettingNode.hpp> #include "../ui/internal/settings/GeodeSettingNode.hpp" @@ -54,30 +55,68 @@ Result<std::shared_ptr<Setting>> Setting::parse( return Err("Setting value is not an object"); } +void Setting::valueChanged() { + SettingChangedEvent(m_modID, shared_from_this()).post(); +} + SettingNode* BoolSetting::createNode(float width) { - return BoolSettingNode::create(shared_from_this(), width); + return BoolSettingNode::create( + std::static_pointer_cast<BoolSetting>(shared_from_this()), + width + ); } SettingNode* IntSetting::createNode(float width) { - return IntSettingNode::create(shared_from_this(), width); + return IntSettingNode::create( + std::static_pointer_cast<IntSetting>(shared_from_this()), + width + ); } SettingNode* FloatSetting::createNode(float width) { - return FloatSettingNode::create(shared_from_this(), width); + return FloatSettingNode::create( + std::static_pointer_cast<FloatSetting>(shared_from_this()), + width + ); } SettingNode* StringSetting::createNode(float width) { - return StringSettingNode::create(shared_from_this(), width); + return StringSettingNode::create( + std::static_pointer_cast<StringSetting>(shared_from_this()), + width + ); } SettingNode* FileSetting::createNode(float width) { - return FileSettingNode::create(shared_from_this(), width); + return FileSettingNode::create( + std::static_pointer_cast<FileSetting>(shared_from_this()), + width + ); } SettingNode* ColorSetting::createNode(float width) { - return ColorSettingNode::create(shared_from_this(), width); + return ColorSettingNode::create( + std::static_pointer_cast<ColorSetting>(shared_from_this()), + width + ); } SettingNode* ColorAlphaSetting::createNode(float width) { - return ColorAlphaSettingNode::create(shared_from_this(), width); + return ColorAlphaSettingNode::create( + std::static_pointer_cast<ColorAlphaSetting>(shared_from_this()), + width + ); +} + +SettingChangedEvent::SettingChangedEvent( + std::string const& modID, + std::shared_ptr<Setting> setting +) : m_modID(modID), m_setting(setting) {} + +std::string SettingChangedEvent::getModID() const { + return m_modID; +} + +std::shared_ptr<Setting> SettingChangedEvent::getSetting() const { + return m_setting; } diff --git a/loader/src/main.cpp b/loader/src/main.cpp index d48f4c9f..62b49a93 100644 --- a/loader/src/main.cpp +++ b/loader/src/main.cpp @@ -1,5 +1,6 @@ #include <Geode/loader/Mod.hpp> #include <Geode/loader/Loader.hpp> +#include <Geode/loader/SettingEvent.hpp> #include <InternalLoader.hpp> #include <InternalMod.hpp> #include <Geode/loader/Log.hpp> @@ -50,6 +51,16 @@ BOOL WINAPI DllMain(HINSTANCE lib, DWORD reason, LPVOID) { } #endif +static SettingChangedEventHandler<BoolSetting> _( + "geode.loader", "show-platform-console", + [](std::shared_ptr<BoolSetting> setting) { + if (setting->getValue()) { + Loader::get()->openPlatformConsole(); + } else { + Loader::get()->closePlatfromConsole(); + } + } +); int geodeEntry(void* platformData) { // setup internals @@ -108,8 +119,7 @@ int geodeEntry(void* platformData) { << "Set up loader"; if (InternalMod::get()->getSettingValue<bool>("show-platform-console")) { - InternalLoader::get()->setupPlatformConsole(); - InternalLoader::get()->awaitPlatformConsole(); + Loader::get()->openPlatformConsole(); } InternalMod::get()->log()