From 4003a9c169c243b412f3e0f0e518e844c49ad3b2 Mon Sep 17 00:00:00 2001 From: Auri Date: Mon, 11 Oct 2021 12:46:57 -0700 Subject: [PATCH] new EventEmitter template class that can have arbitrary event parameters, and is awesome --- src/client/Callback.h | 82 ------ src/client/Input.cpp | 16 +- src/client/Input.h | 36 +-- src/client/Window.cpp | 2 +- src/client/Window.h | 11 +- src/client/graph/Renderer.cpp | 2 +- src/client/graph/Renderer.h | 2 +- src/client/gui/Root.cpp | 4 +- src/client/gui/Root.h | 2 +- src/client/scene/MainMenuScene.cpp | 6 +- src/game/atlas/TextureAtlas.cpp | 11 +- src/game/atlas/TextureAtlas.h | 7 +- src/game/atlas/TextureBuilder.cpp | 14 +- src/lua/LocalLuaParser.cpp | 2 +- src/lua/LocalLuaParser.h | 4 +- src/lua/register/RegisterKeybind.h | 14 +- src/lua/usertype/KeyObserver.cpp | 8 +- src/lua/usertype/KeyObserver.h | 4 +- src/util/EventEmitter.h | 250 ++++++++++++++++++ src/util/FunctionTraits.h | 6 +- src/util/StringParser.h | 62 ++--- src/util/TupleSub.h | 42 +++ src/world/player/LocalPlayer.cpp | 2 +- src/world/player/LocalPlayer.h | 2 +- .../zeus/mods/zeus_vegetation/script/main.lua | 4 +- 25 files changed, 399 insertions(+), 196 deletions(-) delete mode 100644 src/client/Callback.h create mode 100644 src/util/EventEmitter.h create mode 100644 src/util/TupleSub.h diff --git a/src/client/Callback.h b/src/client/Callback.h deleted file mode 100644 index 1fc77b01..00000000 --- a/src/client/Callback.h +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "util/Types.h" - -class CallbackRef { -public: - CallbackRef() = default; - explicit CallbackRef(std::function unbind): unbind(std::make_shared>(unbind)) {}; - ~CallbackRef() { if (unbind && unbind.unique()) unbind->operator()(); } - -private: - sptr> unbind = nullptr; -}; - -template -class CallbackManager { -public: - typedef std::function CB_TYPE; - - void update() { - while (invalidInds.size()) { - callbacks.remove(invalidInds.front()); - invalidInds.pop_front(); - } - }; - - void call(const Args&... args) { - for (const let& cb : callbacks) if (cb.second.valid) cb.second.call(args...); - }; - - CallbackRef bind(const CB_TYPE& cb) { - usize cID = next++; - callbacks.emplace(cID, InvalidatableCB(cb)); - return CallbackRef([cID, this]() { unbind(cID); }); - }; - - void unbind(usize ind) { - callbacks.at(ind).valid = false; - }; - -private: - struct InvalidatableCB { - InvalidatableCB(const CB_TYPE& cb): call(cb) {}; - - CB_TYPE call; - bool valid = true; - }; - - usize next = 0; - std::list invalidInds; - std::unordered_map callbacks; -}; - -template -class CallbackGroup { -public: - typedef E CB_IDENTIFIER; - typedef std::function CB_TYPE; - - void update() { - for (usize i = 0; i < callbacks.size(); i++) callbacks[i].update(); - }; - - void call(CB_IDENTIFIER type, const Args&... args) { - callbacks[static_cast(type)].call(args...); - }; - - CallbackRef bind(CB_IDENTIFIER type, const CB_TYPE& cb) { - return callbacks[static_cast(type)].bind(cb); - }; - - void unbind(CB_IDENTIFIER type, usize ind) { - callbacks[static_cast(type)].unbind(ind); - }; - -private: - array, static_cast(E::_END)> callbacks; -}; diff --git a/src/client/Input.cpp b/src/client/Input.cpp index ef9626fc..95ea10a6 100644 --- a/src/client/Input.cpp +++ b/src/client/Input.cpp @@ -63,19 +63,23 @@ ivec2 Input::getMouseDelta() { void Input::updateKey(u32 key, i32 state) { keyState[key] = state != GLFW_RELEASE && state != 3; - events.call(CBType::KEY, key, state); - events.call(state == GLFW_PRESS ? CBType::KEY_PRESS : - state == GLFW_RELEASE ? CBType::KEY_RELEASE : CBType::KEY_REPEAT, key, state); + + emit(key, state); + if (state == GLFW_PRESS) emit(key); + else if (state == GLFW_RELEASE) emit(key); + else emit(key); } void Input::updateChar(u32 codepoint) { - events.call(CBType::CHAR, codepoint, 0); + emit(codepoint); } void Input::updateMouse(u32 button, i32 state) { mouseState[button] = state != GLFW_RELEASE && state != 3; - events.call(CBType::MOUSE, button, state); - events.call(state != GLFW_RELEASE ? CBType::MOUSE_PRESS : CBType::MOUSE_RELEASE, button, state); + + emit(button, state); + if (state == GLFW_PRESS) emit(button); + else emit(button); } void Input::scrollCallback(GLFWwindow* window, f64 x, f64 y) { diff --git a/src/client/Input.h b/src/client/Input.h index b75bed42..4d55f7b3 100644 --- a/src/client/Input.h +++ b/src/client/Input.h @@ -5,16 +5,34 @@ #include #include "util/Types.h" -#include "client/Callback.h" +#include "util/EventEmitter.h" class GLFWwindow; /** * Manages callbacks for key and mouse input, allows toggling mouse locking. */ + +namespace { + enum class InEvt { + Key, + KeyPress, + KeyRepeat, + KeyRelease, + Char, + Mouse, + MousePress, + MouseRelease + }; +} + +class Input : public EventEmitter< + Event, Event, + Event, Event, Event, + Event, Event, Event> { -class Input { public: + typedef InEvt Event; /** Activates the input listeners. */ void init(GLFWwindow* window); @@ -45,20 +63,6 @@ public: ivec2 getMouseDelta(); - enum class CBType { - KEY, - KEY_PRESS, - KEY_REPEAT, - KEY_RELEASE, - CHAR, - MOUSE, - MOUSE_PRESS, - MOUSE_RELEASE, - _END - }; - - CallbackGroup events {}; - private: /** Calls the key callbacks and sets the key state of the key provided. */ diff --git a/src/client/Window.cpp b/src/client/Window.cpp index 4bd7a015..ad3e5fc2 100644 --- a/src/client/Window.cpp +++ b/src/client/Window.cpp @@ -81,7 +81,7 @@ void Window::resizeCallback(GLFWwindow* window, i32 width, i32 height) { glfwGetFramebufferSize(window, &w->win.x, &w->win.y); glViewport(0, 0, w->win.x, w->win.y); - w->resize.call(ivec2 { width, height }); + w->emit(ivec2 { width, height }); } Window::~Window() { diff --git a/src/client/Window.h b/src/client/Window.h index f023eb74..61ab9022 100644 --- a/src/client/Window.h +++ b/src/client/Window.h @@ -9,11 +9,18 @@ #include #include "util/Types.h" +#include "util/EventEmitter.h" #include "Input.h" -class Window { +namespace { + enum class WinEvt { Resize }; +} + +class Window : public EventEmitter> { public: + typedef WinEvt Event; + Window(); Window(ivec2 win); @@ -34,8 +41,6 @@ public: GLFWwindow* mainWindow = nullptr; - CallbackManager resize {}; - private: static void resizeCallback(GLFWwindow* window, i32 width, i32 height); diff --git a/src/client/graph/Renderer.cpp b/src/client/graph/Renderer.cpp index 2b371ea1..993f3920 100644 --- a/src/client/graph/Renderer.cpp +++ b/src/client/graph/Renderer.cpp @@ -39,7 +39,7 @@ Renderer::Renderer(glm::ivec2 win) : glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - callbacks.emplace_back(window.resize.bind([&](glm::ivec2 win) { + callbacks.emplace_back(window.bind([&](ivec2 win) { ssao.windowResized(win); blur.windowResized(win); light.windowResized(win); diff --git a/src/client/graph/Renderer.h b/src/client/graph/Renderer.h index df90182d..f45507b6 100644 --- a/src/client/graph/Renderer.h +++ b/src/client/graph/Renderer.h @@ -76,6 +76,6 @@ private: GLint currentModelUniform; double elapsedTime = 0; - vec callbacks {}; + vec callbacks {}; }; diff --git a/src/client/gui/Root.cpp b/src/client/gui/Root.cpp index b9592728..ef395418 100644 --- a/src/client/gui/Root.cpp +++ b/src/client/gui/Root.cpp @@ -16,7 +16,7 @@ Gui::Root::Root(Window& window, TextureAtlas& atlas) : { Prop::SIZE, array { Expr(std::to_string(size.x)), Expr(std::to_string(size.y)) }} }}); - callbacks.emplace_back(window.resize.bind([&](ivec2 size) { + callbacks.emplace_back(window.bind([&](ivec2 size) { body->setProp(Prop::SIZE, array { Expr(std::to_string(size.x)), Expr(std::to_string(size.y)) }); Timer t("Resize UI"); @@ -24,7 +24,7 @@ Gui::Root::Root(Window& window, TextureAtlas& atlas) : t.printElapsedMs(); })); - callbacks.emplace_back(window.input.events.bind(Input::CBType::MOUSE, [&](u32 button, i32 state) { + callbacks.emplace_back(window.input.bind([&](u32 button, i32 state) { let pos = window.input.getMousePos(); body->handleMouseClick(pos, button, state == GLFW_PRESS); })); diff --git a/src/client/gui/Root.h b/src/client/gui/Root.h index d60b639f..ebc8ab7f 100644 --- a/src/client/gui/Root.h +++ b/src/client/gui/Root.h @@ -59,7 +59,7 @@ namespace Gui { private: Window& window; - vec callbacks {}; + vec callbacks {}; }; } diff --git a/src/client/scene/MainMenuScene.cpp b/src/client/scene/MainMenuScene.cpp index e60dc53a..cdc4f6be 100644 --- a/src/client/scene/MainMenuScene.cpp +++ b/src/client/scene/MainMenuScene.cpp @@ -105,13 +105,13 @@ MainMenuScene::MainMenuScene(Client& client) : Scene(client), throw std::runtime_error("conf.json is missing 'version'"); const AtlasRef icon = std::filesystem::exists(file.path() / "icon.png") - ? client.game->textures.addFile((file.path() / "icon.png").string(), false) - : client.game->textures["menu_flag_missing"]; + ? client.game->textures.addFile((file.path() / "icon.png").string(), false, file.path().stem().string()) + : client.game->textures.get("menu_flag_missing"); subgames.emplace_back(SubgameConfig { json["name"], json["description"], json["version"] }, icon, file.path()); } - catch(const std::runtime_error& e) { + catch (const std::runtime_error& e) { std::cout << Log::err << "Failed to load subgame '" << file.path().filename().string() << "', " << e.what() << "." << std::endl; } diff --git a/src/game/atlas/TextureAtlas.cpp b/src/game/atlas/TextureAtlas.cpp index 73049466..09e89c70 100644 --- a/src/game/atlas/TextureAtlas.cpp +++ b/src/game/atlas/TextureAtlas.cpp @@ -5,13 +5,15 @@ #include "util/Log.h" #include "util/Util.h" +#include "game/atlas/TextureBuilder.h" #include "game/atlas/asset/AtlasTexture.h" TextureAtlas::TextureAtlas(uvec2 size) : canvasSize(size), canvasTileSize(size / 16u), tilesTotal(canvasTileSize.x * canvasTileSize.y), - tiles(canvasTileSize.x * canvasTileSize.y, false) { + tiles(canvasTileSize.x * canvasTileSize.y, false), + builder(make_shared(*this)) { // Get GPU texture capabilites and log it. i32 maxTexSize, texUnits; @@ -34,12 +36,13 @@ vec TextureAtlas::addDirectory(const std::filesystem::path& path, bool return refs; } -AtlasRef TextureAtlas::addFile(const std::filesystem::path& path, bool persistent) { +AtlasRef TextureAtlas::addFile(const std::filesystem::path& path, bool persistent, string identifier) { + if (identifier.empty()) identifier = path.stem().string(); i32 width, height; u8* rawData = stbi_load(path.string().data(), &width, &height, nullptr, 4); vec data(rawData, rawData + width * height * 4); free(rawData); - let ref = addBytes(path.stem().string(), persistent, u16vec2(width, height), data); + let ref = addBytes(identifier, persistent, u16vec2(width, height), data); return ref; } @@ -75,7 +78,7 @@ AtlasRef TextureAtlas::operator[](const string& identifier) { const let tex = get(identifier); if (tex->getIdentifier() != "_missing") return tex; - let data = texBuilder.build(identifier); + let data = builder->build(identifier); let texture = (std::holds_alternative(data.texture->data)) ? addBytes(identifier, false, data.texture->size, data.texture->getBytes()) : addCrop(identifier, false, std::get(data.texture->data).offset, diff --git a/src/game/atlas/TextureAtlas.h b/src/game/atlas/TextureAtlas.h index aaf86656..0af30a98 100644 --- a/src/game/atlas/TextureAtlas.h +++ b/src/game/atlas/TextureAtlas.h @@ -6,11 +6,10 @@ #include #include "util/Types.h" -#include "util/StringParser.h" #include "client/graph/Texture.h" -#include "game/atlas/TextureBuilder.h" class AtlasTexture; +class TextureBuilder; /** * Manages loading and generating images into a single atlas texture, @@ -25,7 +24,7 @@ public: vec> addDirectory(const std::filesystem::path& path, bool persistent); - sptr addFile(const std::filesystem::path& path, bool persistent); + sptr addFile(const std::filesystem::path& path, bool persistent, string identifier = ""); sptr addBytes(const string& identifier, bool persistent, u16vec2 size, const vec& data); @@ -64,6 +63,6 @@ private: std::unordered_set> persistentTextures {}; Texture texture {}; - const TextureBuilder texBuilder { *this }; + sptr builder; }; diff --git a/src/game/atlas/TextureBuilder.cpp b/src/game/atlas/TextureBuilder.cpp index 4da226ff..ebed39fc 100644 --- a/src/game/atlas/TextureBuilder.cpp +++ b/src/game/atlas/TextureBuilder.cpp @@ -132,14 +132,14 @@ TextureBuilder::Data TextureBuilder::Data::tint(u32 tint, Data tex, optionalrenderer.window.input, refs, iden); }); + [&](const string& iden) { RegisterKeybind::client(core, client->renderer.window.input, listeners, iden); }); Api::Util::createRegister(lua, core, "blockmodel"); Api::Util::createRegister(lua, core, "entity", nullptr, "entities"); diff --git a/src/lua/LocalLuaParser.h b/src/lua/LocalLuaParser.h index f57c13ac..f6faa03f 100644 --- a/src/lua/LocalLuaParser.h +++ b/src/lua/LocalLuaParser.h @@ -10,8 +10,8 @@ #include "lua/LuaParser.h" #include "Mod.h" -#include "client/Callback.h" #include "util/CovariantPtr.h" +#include "util/EventEmitter.h" #include "lua/LuaKeybindHandler.h" class Client; @@ -48,8 +48,8 @@ private: PlayerPtr player; double accumulatedDelta = 0; - vec refs; vec modOrder {}; + vec listeners; std::unordered_set kbObservers {}; std::unordered_map mods {}; }; diff --git a/src/lua/register/RegisterKeybind.h b/src/lua/register/RegisterKeybind.h index a1b25dbb..33ffe7b9 100644 --- a/src/lua/register/RegisterKeybind.h +++ b/src/lua/register/RegisterKeybind.h @@ -16,17 +16,17 @@ namespace RegisterKeybind { * @param handler - The keybind handler to add the keybind to. */ - static void registerKeybind(Input& input, vec& refs, sol::table keybinds, const string& identifier) { + static void registerKeybind(Input& input, vec& listeners, sol::table keybinds, const string& identifier) { sol::table keybindTbl = keybinds[identifier]; u32 def = keybindTbl.get("default"); auto onPress = keybindTbl.get>("on_press"); auto onRelease = keybindTbl.get>("on_release"); - if (onPress) refs.emplace_back(input.events.bind(Input::CBType::KEY_PRESS, [=](u32 key, i32) { - if (key == def) (*onPress)(); })); - if (onRelease) refs.emplace_back(input.events.bind(Input::CBType::KEY_RELEASE, [=](u32 key, i32) { - if (key == def) (*onRelease)(); })); + if (onPress) listeners.emplace_back(input.bind( + [=](u32 key) { if (key == def) (*onPress)(); })); + if (onRelease) listeners.emplace_back(input.bind( + [=](u32 key) { if (key == def) (*onRelease)(); })); } } @@ -41,7 +41,7 @@ namespace RegisterKeybind { * @param identifier - The identifier of the keybind to add. */ - static void client(sol::table& core, Input& input, vec& refs, const string& identifier) { - registerKeybind(input, refs, core.get("registered_keybinds"), identifier); + static void client(sol::table& core, Input& input, vec& listeners, const string& identifier) { + registerKeybind(input, listeners, core.get("registered_keybinds"), identifier); } }; \ No newline at end of file diff --git a/src/lua/usertype/KeyObserver.cpp b/src/lua/usertype/KeyObserver.cpp index 4894a8df..e6e0c89f 100644 --- a/src/lua/usertype/KeyObserver.cpp +++ b/src/lua/usertype/KeyObserver.cpp @@ -19,10 +19,10 @@ string utf8chr(i32 cp) { usize Api::Usertype::KeyObserver::ID_NEXT = 0; void Api::Usertype::KeyObserver::start() { - if (nativeCBs.size()) return; + if (listeners.size()) return; parser.addKBObserver(id); - nativeCBs.emplace_back(input.events.bind(Input::CBType::KEY, [&](i32 key, u32 state) { + listeners.emplace_back(input.bind([&](i32 key, u32 state) { if (state == GLFW_PRESS && on_press) on_press(key); else if (state == GLFW_RELEASE && on_release) on_release(key); @@ -32,7 +32,7 @@ void Api::Usertype::KeyObserver::start() { } })); - nativeCBs.emplace_back(input.events.bind(Input::CBType::CHAR, [&](i32 codepoint, u32) { + listeners.emplace_back(input.bind([&](i32 codepoint) { buffer += utf8chr(codepoint); if (on_change) on_change(buffer); })); @@ -40,7 +40,7 @@ void Api::Usertype::KeyObserver::start() { void Api::Usertype::KeyObserver::stop() { parser.removeKBObserver(id); - nativeCBs.clear(); + listeners.clear(); } const string& Api::Usertype::KeyObserver::getBuffer() { diff --git a/src/lua/usertype/KeyObserver.h b/src/lua/usertype/KeyObserver.h index baaa5711..5257f1e3 100644 --- a/src/lua/usertype/KeyObserver.h +++ b/src/lua/usertype/KeyObserver.h @@ -2,7 +2,7 @@ #include "lua/Lua.h" #include "util/Types.h" -#include "client/Callback.h" +#include "util/EventEmitter.h" class Input; class LocalLuaParser; @@ -35,7 +35,7 @@ namespace Api::Usertype { Input& input; LocalLuaParser& parser; - vec nativeCBs {}; + vec listeners {}; static usize ID_NEXT; }; diff --git a/src/util/EventEmitter.h b/src/util/EventEmitter.h new file mode 100644 index 00000000..d2131846 --- /dev/null +++ b/src/util/EventEmitter.h @@ -0,0 +1,250 @@ +#pragma once + +#include +#include + +#include "util/Types.h" + +/** Supporting functions and structs for EventEmitter. */ +namespace detail { + + /** + * Stores a listener function and a validity, when the listener is unbound, + * it will have the `valid` member set to false instead of removing it from the listeners map directly, + * to avoid iterator offset errors when listeners are removed during an emit. + * The invalidatable listener will then be removed next time an event is emitted. + * + * @tparam E - The event type that the listener is for. + */ + + template + struct InvalidatableListener { + InvalidatableListener(const typename E::function_type& listener): listener(listener) {} + + /** The listener function. */ + typename E::function_type listener; + + /** Whether or not the function is still valid. */ + bool valid = true; + }; + + /** + * A nasty recursive compile time function that takes a tuple of event types and + * generates a tuple of lists of pairs of event indices and maps (oh god). + * This type is then initialized to store the listeners on the EventEmitter. + * + * getEventListTuple(std::tuple, Event) = + * std::tuple< + * std::list>>, + * std::list>> + * > + * + * @tparam E - The events tuple to generate the list off of. + * @returns a tuple type as specified above. + */ + + template + inline constexpr auto getEventListTuple() { + if constexpr (N < std::tuple_size_v) { + return getEventListTuple>>>>(); + } + else { + return std::tuple{}; + } + } +} + +/** + * A reference to a listener added using `bind`. + * When all instances of it go out of scope, the listener + * that was bound to return this instance will be unbound from its emitter. + */ + +class ListenerRef { +public: + ListenerRef() = default; + explicit ListenerRef(std::function unbind): unbind(make_shared>(unbind)) {}; + ~ListenerRef() { if (unbind && unbind.unique()) unbind->operator()(); } + +private: + sptr> unbind = nullptr; +}; + +/** + * Specifies a single event of an EventEmitter, with an id and parameter list. + * Provided to a specialization of the EventEmitter class to specify its events. + * + * @tparam T - The integral identifier for the event. + * @tparam Args - The arguments that will be provided to listener functions. + */ + +template +struct Event { + + /** The integral identifier of the event as a u32. */ + static constexpr u32 id_type = (u32)T; + + /** A tuple of the event parameters. */ + typedef std::tuple args_type; + + /** The type of the event listeners for this event. */ + typedef std::function function_type; +}; + +/** + * Manages listeners for a set of events specified at compile time, + * allowing event listeners to be bound and events to be emitted at runtime. + * Each event may have its own parameter types. Objects that should emit events should + * derive from this class publicly, and make available an Event typedef that + * corresponds to the valid event identifiers provided to this class. + * + * @tparam E - The list of Events this listener should contain. + */ + +template +class EventEmitter { + + /** A tuple of the provided event types. */ + typedef std::tuple event_types; + + /** The type of the event list, which contains event listeners. */ + typedef decltype(detail::getEventListTuple()) event_list; + +public: + + /** + * Binds a listener to the specified event, + * which will be unbound when the ref goes out of scope. + * + * @tparam T - The event identifier to bind to. + * @param listener - The event listener, its parameters must match the event parameters. + * @returns a ListenerRef corresponding to the listener, which will unbind the listener when it goes out of scope. + */ + + template + ListenerRef bind(const L& listener) { + usize ind = bindRaw(listener); + return ListenerRef {[ind, this]() { unbind(ind); }}; + } + + /** + * Binds a listener to the specified event, returning the raw listener id, + * which must be used to manually unbind the event later. + * Usually, `bind` should be called instead. + * + * @tparam T - The event identifier to bind to. + * @param listener - The event listener, its parameters must match the event parameters. + * @returns the listener's id, to be used in `unbind`. + */ + + template + usize bindRaw(const L& listener) { + return addEventListener(listener); + } + + /** + * Unbinds the listener matching the ind from the specified event. + * Usually this won't need to be called, as ListenerRef handles it automatically. + * + * @tparam T - The event identifier to unbind the listener from. + * @param ind - The id of the bound listener. + */ + + template + void unbind(usize ind) { + return removeEventListener(ind); + } + +protected: + + /** + * Emits the specified event with the arguments provided. + * The arguments must match the Event specification. + * + * @tparam T - The event identifier to emit. + * @param args - The arguments to pass to the listener functions. + */ + + template + void emit(const Args&... args) { + invokeEventListeners(args...); + } + +private: + + /** + * Recursively finds the right list to add the specified + * listener to at compile time, adds it, and returns its index. + * + * @tparam T - The event identifier to bind to. + * @param listener - The listener function. + * @returns the index of the bound event listener. + */ + + template + inline usize addEventListener(const L& listener) { + if constexpr (std::tuple_element_t::id_type == static_cast(T)) { + let& list = std::get(eventList); + usize ind = list.first++; + list.second.emplace(ind, detail::InvalidatableListener< + std::tuple_element_t> { listener }); + return ind; + } + else { + return addEventListener(listener); + } + } + + /** + * Recursively finds the right list to remove the specified + * listener from at compile time, and marks it as invaid. + * + * @tparam T - The event identifier to unbind from. + * @param listener - The listener function. + */ + + template + inline void removeEventListener(const usize& ind) { + if constexpr (std::tuple_element_t::id_type == static_cast(T)) { + let& list = std::get(eventList).second; + list.at(ind).valid = false; + return; + } + else { + return removeEventListener(ind); + } + } + + /** + * Recursively finds the right list to invoke the listeners of, + * removes all invalid listeners, and invokes the remaining ones. + * + * @tparam T - The event identifier to invoke. + * @param args - The arguments to pass to the listener functions. + */ + + template + inline void invokeEventListeners(const Args&... args) { + if constexpr (std::tuple_element_t::id_type == static_cast(T)) { + let& list = std::get(eventList).second; + for (let it = list.begin(); it != list.end();) { + if (it->second.valid) { + it->second.listener(args...); + it++; + } + else { + it = list.erase(it); + } + } + return; + } + else { + return invokeEventListeners(args...); + } + } + + /** The list of event listeners. */ + event_list eventList {}; +}; \ No newline at end of file diff --git a/src/util/FunctionTraits.h b/src/util/FunctionTraits.h index ee3dedf2..8b5e502e 100644 --- a/src/util/FunctionTraits.h +++ b/src/util/FunctionTraits.h @@ -1,9 +1,11 @@ #pragma once +#include + /** * Template struct that takes a function pointer, method pointer, or lambda and * exposes the corresponding std::function type, arguments types, and return type. - * From the god over at https://stackoverflow.com/q/21657627 (thank you thank you) + * Adapted from the god over at https://stackoverflow.com/q/21657627 (thank you god) */ template @@ -28,4 +30,4 @@ struct function_traits { typedef std::function function_type; typedef std::tuple args_type; typedef R return_type; -}; \ No newline at end of file +}; diff --git a/src/util/StringParser.h b/src/util/StringParser.h index 0fc8ed2b..4fe6b77c 100644 --- a/src/util/StringParser.h +++ b/src/util/StringParser.h @@ -4,6 +4,7 @@ #include #include "util/Types.h" +#include "util/TupleSub.h" #include "util/FunctionTraits.h" /** SFINAE Helpers. */ @@ -65,19 +66,19 @@ public: /** * Adds a function to the functions map. - * For a function to be valid, it must only have parameters that are - * integral, floating point, strings, the Data type, or optionals & variants of them. - * It must also return a Data type. - * If the function needs access to the Context, use addFnCtx() instead. + * For a function to be valid, it must only have parameters that are integral, floating point, + * strings, the Data type, or optionals & variants of them, and return a Data instance. + * It may have a reference to the context as its first argument. * * @param name - The name of the function. - * @param fn - The function lambda. + * @param fn - The function. */ - template + template ::args_type, + std::enable_if_t, C&>, bool> = true> + void addFn(const string& name, const L& fn) { functions.emplace(name, [=, this](C& ctx, STR_ARGS strArgs) { - typedef typename function_traits::args_type Args; Args args = {}; parseStrArgs(strArgs, args, ctx); return std::apply(fn, args); @@ -85,50 +86,25 @@ public: } /** - * Adds a function to the functions map that has access to the Ctx. - * The same restrictions for a function apply, but the function - * must also accept a reference to a Ctx before all other arguments. - * This parameter should not be specified in Args. + * Specialization for functions containing context. + * For some reason, trying to do a constexpr if to do this logic + * conditionally in the above function doesn't work. * - * @tparam Args - The argument types of the function. * @param name - The name of the function. - * @param fn - The function lambda. + * @param fn - The function. */ - - template - void addFnCtx(const string& name, const Func& fn) { + + template ::args_type, + std::enable_if_t, C&>, bool> = true> + + void addFn(const string& name, const L& fn) { functions.emplace(name, [=, this](C& ctx, STR_ARGS strArgs) { - std::tuple args = {}; - parseStrArgs>(strArgs, args, ctx); + tuple_sub_t args = {}; + parseStrArgs>(strArgs, args, ctx); return std::apply(fn, std::tuple_cat(std::tuple(ctx), args)); }); } - /** - * Shortcut to add the literal function, which is called when a string literal is found. - * This is shorthand for calling addFn("_", ...). - * The literal function must only accept a single string parameter. - * - * @param fn - The literal function lambda. - */ - - template - inline void addLiteralFn(const Func& fn) { - addFn("_", fn); - } - - /** - * Shortcut to add the literal function that has access to the Ctx. - * The same restrictions for a literal function apply, but the function - * must also accept a reference to a Ctx before the string parameter. - * - * @param fn - The literal function lambda. - */ - - template - inline void addLiteralFnCtx(const Func& fn) { - addFnCtx("_", fn); - } /** * Parses a string using the functions defined. diff --git a/src/util/TupleSub.h b/src/util/TupleSub.h new file mode 100644 index 00000000..34a7c4b5 --- /dev/null +++ b/src/util/TupleSub.h @@ -0,0 +1,42 @@ +#pragma once + +namespace { + + /** + * Recursively generates a tuple out of a subsection of another tuple. + * + * @tparam T - The base tuple to pull the types from. + * @tparam N - The current index of the type to add to the resultant tuple. + * @tparam M - The end index of types to pull. + * @tparam Args - A pack of the args to add into the resultant tuple. + * @returns the subsection of the tuple T as specified by M and N. + */ + + template + inline constexpr auto tuple_sub_impl() { + if constexpr (N <= M) return tuple_sub_impl>(); + else return std::tuple {}; + } +} + +/** + * Struct containing a subsection of a tuple T and start index N and end index M. + * + * @tparam T - The tuple to take a subsection of. + * @tparam N - The start index of the tuple types to include. + * @tparam M - The end index of the tuple types to include - defaults to the tuple's size. + */ + +template - 1> +struct tuple_sub { + + /** The value of the subsection of the tuple. */ + typedef decltype(tuple_sub_impl()) value_type; +}; + +/** + * Shorthand for the value type of tuple_sub with the provided template arguments. + */ + +template - 1> +using tuple_sub_t = typename tuple_sub::value_type; \ No newline at end of file diff --git a/src/world/player/LocalPlayer.cpp b/src/world/player/LocalPlayer.cpp index 789a5a2d..3aeede5a 100644 --- a/src/world/player/LocalPlayer.cpp +++ b/src/world/player/LocalPlayer.cpp @@ -42,7 +42,7 @@ LocalPlayer::LocalPlayer(SubgamePtr game, LocalWorld& world, DimensionPtr dim, R indicators->append(); - callbacks.emplace_back(renderer.window.input.events.bind(Input::CBType::MOUSE_PRESS, [&](u32 button, i32) { + listeners.emplace_back(renderer.window.input.bind([&](u32 button) { if (button == GLFW_MOUSE_BUTTON_LEFT) mouseLeftClicked = true; else if (button == GLFW_MOUSE_BUTTON_RIGHT) mouseRightClicked = true; })); diff --git a/src/world/player/LocalPlayer.h b/src/world/player/LocalPlayer.h index f4263248..9549ef29 100644 --- a/src/world/player/LocalPlayer.h +++ b/src/world/player/LocalPlayer.h @@ -150,6 +150,6 @@ private: /** The actual wield-item model, set to the currently held item. */ DrawableEntity handItemModel; - vec callbacks; + vec listeners; }; diff --git a/subgames/zeus/mods/zeus_vegetation/script/main.lua b/subgames/zeus/mods/zeus_vegetation/script/main.lua index 05d3679a..6e7eae8e 100644 --- a/subgames/zeus/mods/zeus_vegetation/script/main.lua +++ b/subgames/zeus/mods/zeus_vegetation/script/main.lua @@ -42,8 +42,8 @@ register_vegetation('mushroom_brown', { name = 'Brown Mushroom', model = 'zepha: register_vegetation('clover', { model = 'zeus:vegetation:clover', textures = { - '(canvas(16),tint(0, crop(0, 0, 16, 16, zeus:vegetation:clover)))', - '(canvas(16),tint(0, crop(16, 0, 16, 16, zeus:vegetation:clover)))' + 'tint(0, crop(0, 0, 16, 16, zeus:vegetation:clover))', + 'tint(0, crop(16, 0, 16, 16, zeus:vegetation:clover))' }, selection_box = { { 1/16, 0, 1/16, 15/16, 4/16, 15/16 }