new EventEmitter template class that can have arbitrary event parameters, and is awesome
parent
b7825b84ce
commit
4003a9c169
|
@ -1,82 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
|
||||
#include "util/Types.h"
|
||||
|
||||
class CallbackRef {
|
||||
public:
|
||||
CallbackRef() = default;
|
||||
explicit CallbackRef(std::function<void()> unbind): unbind(std::make_shared<std::function<void()>>(unbind)) {};
|
||||
~CallbackRef() { if (unbind && unbind.unique()) unbind->operator()(); }
|
||||
|
||||
private:
|
||||
sptr<std::function<void()>> unbind = nullptr;
|
||||
};
|
||||
|
||||
template <typename... Args>
|
||||
class CallbackManager {
|
||||
public:
|
||||
typedef std::function<void(Args...)> 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<usize> invalidInds;
|
||||
std::unordered_map<usize, InvalidatableCB> callbacks;
|
||||
};
|
||||
|
||||
template <typename E, typename... Args>
|
||||
class CallbackGroup {
|
||||
public:
|
||||
typedef E CB_IDENTIFIER;
|
||||
typedef std::function<void(Args...)> 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<usize>(type)].call(args...);
|
||||
};
|
||||
|
||||
CallbackRef bind(CB_IDENTIFIER type, const CB_TYPE& cb) {
|
||||
return callbacks[static_cast<usize>(type)].bind(cb);
|
||||
};
|
||||
|
||||
void unbind(CB_IDENTIFIER type, usize ind) {
|
||||
callbacks[static_cast<usize>(type)].unbind(ind);
|
||||
};
|
||||
|
||||
private:
|
||||
array<CallbackManager<Args...>, static_cast<usize>(E::_END)> callbacks;
|
||||
};
|
|
@ -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<Event::Key>(key, state);
|
||||
if (state == GLFW_PRESS) emit<Event::KeyPress>(key);
|
||||
else if (state == GLFW_RELEASE) emit<Event::KeyRelease>(key);
|
||||
else emit<Event::KeyRepeat>(key);
|
||||
}
|
||||
|
||||
void Input::updateChar(u32 codepoint) {
|
||||
events.call(CBType::CHAR, codepoint, 0);
|
||||
emit<Event::Char>(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<Event::Mouse>(button, state);
|
||||
if (state == GLFW_PRESS) emit<Event::MousePress>(button);
|
||||
else emit<Event::MouseRelease>(button);
|
||||
}
|
||||
|
||||
void Input::scrollCallback(GLFWwindow* window, f64 x, f64 y) {
|
||||
|
|
|
@ -5,16 +5,34 @@
|
|||
#include <unordered_map>
|
||||
|
||||
#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<InEvt::Key, u32, i32>, Event<InEvt::KeyPress, u32>,
|
||||
Event<InEvt::KeyRepeat, u32>, Event<InEvt::KeyRelease, u32>, Event<InEvt::Char, u32>,
|
||||
Event<InEvt::Mouse, u32, i32>, Event<InEvt::MousePress, u32>, Event<InEvt::MouseRelease, u32>> {
|
||||
|
||||
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<CBType, u32, i32> events {};
|
||||
|
||||
private:
|
||||
|
||||
/** Calls the key callbacks and sets the key state of the key provided. */
|
||||
|
|
|
@ -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<Event::Resize>(ivec2 { width, height });
|
||||
}
|
||||
|
||||
Window::~Window() {
|
||||
|
|
|
@ -9,11 +9,18 @@
|
|||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include "util/Types.h"
|
||||
#include "util/EventEmitter.h"
|
||||
|
||||
#include "Input.h"
|
||||
|
||||
class Window {
|
||||
namespace {
|
||||
enum class WinEvt { Resize };
|
||||
}
|
||||
|
||||
class Window : public EventEmitter<Event<WinEvt::Resize, ivec2>> {
|
||||
public:
|
||||
typedef WinEvt Event;
|
||||
|
||||
Window();
|
||||
|
||||
Window(ivec2 win);
|
||||
|
@ -34,8 +41,6 @@ public:
|
|||
|
||||
GLFWwindow* mainWindow = nullptr;
|
||||
|
||||
CallbackManager<ivec2> resize {};
|
||||
|
||||
private:
|
||||
static void resizeCallback(GLFWwindow* window, i32 width, i32 height);
|
||||
|
||||
|
|
|
@ -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<Window::Event::Resize>([&](ivec2 win) {
|
||||
ssao.windowResized(win);
|
||||
blur.windowResized(win);
|
||||
light.windowResized(win);
|
||||
|
|
|
@ -76,6 +76,6 @@ private:
|
|||
GLint currentModelUniform;
|
||||
double elapsedTime = 0;
|
||||
|
||||
vec<CallbackRef> callbacks {};
|
||||
vec<ListenerRef> callbacks {};
|
||||
};
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ Gui::Root::Root(Window& window, TextureAtlas& atlas) :
|
|||
{ Prop::SIZE, array<Expr, 2> { 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<Window::Event::Resize>([&](ivec2 size) {
|
||||
body->setProp(Prop::SIZE, array<Expr, 2> {
|
||||
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<Input::Event::Mouse>([&](u32 button, i32 state) {
|
||||
let pos = window.input.getMousePos();
|
||||
body->handleMouseClick(pos, button, state == GLFW_PRESS);
|
||||
}));
|
||||
|
|
|
@ -59,7 +59,7 @@ namespace Gui {
|
|||
private:
|
||||
|
||||
Window& window;
|
||||
vec<CallbackRef> callbacks {};
|
||||
vec<ListenerRef> callbacks {};
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<TextureBuilder>(*this)) {
|
||||
|
||||
// Get GPU texture capabilites and log it.
|
||||
i32 maxTexSize, texUnits;
|
||||
|
@ -34,12 +36,13 @@ vec<AtlasRef> 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<u8> 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<TextureBuilder::Texture::ByteData>(data.texture->data))
|
||||
? addBytes(identifier, false, data.texture->size, data.texture->getBytes())
|
||||
: addCrop(identifier, false, std::get<TextureBuilder::Texture::CropData>(data.texture->data).offset,
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
#include <unordered_map>
|
||||
|
||||
#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<sptr<AtlasTexture>> addDirectory(const std::filesystem::path& path, bool persistent);
|
||||
|
||||
sptr<AtlasTexture> addFile(const std::filesystem::path& path, bool persistent);
|
||||
sptr<AtlasTexture> addFile(const std::filesystem::path& path, bool persistent, string identifier = "");
|
||||
|
||||
sptr<AtlasTexture> addBytes(const string& identifier, bool persistent, u16vec2 size, const vec<u8>& data);
|
||||
|
||||
|
@ -64,6 +63,6 @@ private:
|
|||
std::unordered_set<sptr<AtlasTexture>> persistentTextures {};
|
||||
|
||||
Texture texture {};
|
||||
const TextureBuilder texBuilder { *this };
|
||||
sptr<TextureBuilder> builder;
|
||||
};
|
||||
|
||||
|
|
|
@ -132,14 +132,14 @@ TextureBuilder::Data TextureBuilder::Data::tint(u32 tint, Data tex, optional<Dat
|
|||
}
|
||||
|
||||
TextureBuilder::TextureBuilder(TextureAtlas& atlas): ctx({ atlas }) {
|
||||
parser.addLiteralFnCtx(&TextureBuilder::Data::literal);
|
||||
parser.addFn("", &TextureBuilder::Data::stack);
|
||||
parser.addFn("_", &Data::literal);
|
||||
parser.addFn("", &Data::stack);
|
||||
|
||||
parser.addFn("canvas", &TextureBuilder::Data::canvas);
|
||||
parser.addFn("alpha", &TextureBuilder::Data::alpha);
|
||||
parser.addFn("crop", &TextureBuilder::Data::crop);
|
||||
parser.addFn("multiply", &TextureBuilder::Data::multiply);
|
||||
parser.addFn("tint", &TextureBuilder::Data::tint);
|
||||
parser.addFn("canvas", &Data::canvas);
|
||||
parser.addFn("alpha", &Data::alpha);
|
||||
parser.addFn("crop", &Data::crop);
|
||||
parser.addFn("multiply", &Data::multiply);
|
||||
parser.addFn("tint", &Data::tint);
|
||||
}
|
||||
|
||||
TextureBuilder::Data TextureBuilder::build(const string& str) const {
|
||||
|
|
|
@ -154,7 +154,7 @@ void LocalLuaParser::loadApi(WorldPtr world) {
|
|||
Api::Util::createRegister(lua, core, "biome",
|
||||
[&](const string& iden) { RegisterBiome::client(core, game, iden); });
|
||||
Api::Util::createRegister(lua, core, "keybind",
|
||||
[&](const string& iden) { RegisterKeybind::client(core, client->renderer.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");
|
||||
|
||||
|
|
|
@ -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<CallbackRef> refs;
|
||||
vec<string> modOrder {};
|
||||
vec<ListenerRef> listeners;
|
||||
std::unordered_set<usize> kbObservers {};
|
||||
std::unordered_map<string, Mod> mods {};
|
||||
};
|
||||
|
|
|
@ -16,17 +16,17 @@ namespace RegisterKeybind {
|
|||
* @param handler - The keybind handler to add the keybind to.
|
||||
*/
|
||||
|
||||
static void registerKeybind(Input& input, vec<CallbackRef>& refs, sol::table keybinds, const string& identifier) {
|
||||
static void registerKeybind(Input& input, vec<ListenerRef>& listeners, sol::table keybinds, const string& identifier) {
|
||||
sol::table keybindTbl = keybinds[identifier];
|
||||
u32 def = keybindTbl.get<unsigned short>("default");
|
||||
|
||||
auto onPress = keybindTbl.get<sol::optional<sol::function>>("on_press");
|
||||
auto onRelease = keybindTbl.get<sol::optional<sol::function>>("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<Input::Event::KeyPress>(
|
||||
[=](u32 key) { if (key == def) (*onPress)(); }));
|
||||
if (onRelease) listeners.emplace_back(input.bind<Input::Event::KeyRelease>(
|
||||
[=](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<CallbackRef>& refs, const string& identifier) {
|
||||
registerKeybind(input, refs, core.get<sol::table>("registered_keybinds"), identifier);
|
||||
static void client(sol::table& core, Input& input, vec<ListenerRef>& listeners, const string& identifier) {
|
||||
registerKeybind(input, listeners, core.get<sol::table>("registered_keybinds"), identifier);
|
||||
}
|
||||
};
|
|
@ -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<Input::Event::Key>([&](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<Input::Event::Char>([&](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() {
|
||||
|
|
|
@ -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<CallbackRef> nativeCBs {};
|
||||
vec<ListenerRef> listeners {};
|
||||
|
||||
static usize ID_NEXT;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,250 @@
|
|||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <functional>
|
||||
|
||||
#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<typename E>
|
||||
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<A, ...>, Event<B, ...>) =
|
||||
* std::tuple<
|
||||
* std::list<usize, std::unordered_map<InvalidatableListener<A>>>,
|
||||
* std::list<usize, std::unordered_map<InvalidatableListener<B>>>
|
||||
* >
|
||||
*
|
||||
* @tparam E - The events tuple to generate the list off of.
|
||||
* @returns a tuple type as specified above.
|
||||
*/
|
||||
|
||||
template<typename E, usize N = 0, typename... Args>
|
||||
inline constexpr auto getEventListTuple() {
|
||||
if constexpr (N < std::tuple_size_v<E>) {
|
||||
return getEventListTuple<E, N + 1, Args..., std::pair<usize,
|
||||
std::unordered_map<usize, InvalidatableListener<
|
||||
typename std::tuple_element_t<N, E>>>>>();
|
||||
}
|
||||
else {
|
||||
return std::tuple<Args...>{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<void()> unbind): unbind(make_shared<std::function<void()>>(unbind)) {};
|
||||
~ListenerRef() { if (unbind && unbind.unique()) unbind->operator()(); }
|
||||
|
||||
private:
|
||||
sptr<std::function<void()>> 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 <let T, typename... Args>
|
||||
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...> args_type;
|
||||
|
||||
/** The type of the event listeners for this event. */
|
||||
typedef std::function<void(Args...)> 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 <typename... E>
|
||||
class EventEmitter {
|
||||
|
||||
/** A tuple of the provided event types. */
|
||||
typedef std::tuple<E...> event_types;
|
||||
|
||||
/** The type of the event list, which contains event listeners. */
|
||||
typedef decltype(detail::getEventListTuple<event_types>()) 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 <let T, typename L>
|
||||
ListenerRef bind(const L& listener) {
|
||||
usize ind = bindRaw<T>(listener);
|
||||
return ListenerRef {[ind, this]() { unbind<T>(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 <let T, typename L>
|
||||
usize bindRaw(const L& listener) {
|
||||
return addEventListener<T>(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 <let T>
|
||||
void unbind(usize ind) {
|
||||
return removeEventListener<T>(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 <let T, typename... Args>
|
||||
void emit(const Args&... args) {
|
||||
invokeEventListeners<T>(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<let T, usize N = 0, typename L>
|
||||
inline usize addEventListener(const L& listener) {
|
||||
if constexpr (std::tuple_element_t<N, event_types>::id_type == static_cast<u32>(T)) {
|
||||
let& list = std::get<N>(eventList);
|
||||
usize ind = list.first++;
|
||||
list.second.emplace(ind, detail::InvalidatableListener<
|
||||
std::tuple_element_t<N, event_types>> { listener });
|
||||
return ind;
|
||||
}
|
||||
else {
|
||||
return addEventListener<T, N + 1>(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<let T, usize N = 0>
|
||||
inline void removeEventListener(const usize& ind) {
|
||||
if constexpr (std::tuple_element_t<N, event_types>::id_type == static_cast<u32>(T)) {
|
||||
let& list = std::get<N>(eventList).second;
|
||||
list.at(ind).valid = false;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
return removeEventListener<T, N + 1>(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<let T, usize N = 0, typename... Args>
|
||||
inline void invokeEventListeners(const Args&... args) {
|
||||
if constexpr (std::tuple_element_t<N, event_types>::id_type == static_cast<u32>(T)) {
|
||||
let& list = std::get<N>(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<T, N + 1>(args...);
|
||||
}
|
||||
}
|
||||
|
||||
/** The list of event listeners. */
|
||||
event_list eventList {};
|
||||
};
|
|
@ -1,9 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
/**
|
||||
* 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 <typename T>
|
||||
|
@ -28,4 +30,4 @@ struct function_traits<R(*)(Args...)> {
|
|||
typedef std::function<R(Args...)> function_type;
|
||||
typedef std::tuple<Args...> args_type;
|
||||
typedef R return_type;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <unordered_map>
|
||||
|
||||
#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 <typename L>
|
||||
template <typename L, typename Args = typename function_traits<L>::args_type,
|
||||
std::enable_if_t<!std::is_same_v<std::tuple_element_t<0, Args>, C&>, bool> = true>
|
||||
|
||||
void addFn(const string& name, const L& fn) {
|
||||
functions.emplace(name, [=, this](C& ctx, STR_ARGS strArgs) {
|
||||
typedef typename function_traits<L>::args_type Args;
|
||||
Args args = {};
|
||||
parseStrArgs<Args>(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 <typename... Args, typename Func>
|
||||
void addFnCtx(const string& name, const Func& fn) {
|
||||
|
||||
template <typename L, typename Args = typename function_traits<L>::args_type,
|
||||
std::enable_if_t<std::is_same_v<std::tuple_element_t<0, Args>, C&>, bool> = true>
|
||||
|
||||
void addFn(const string& name, const L& fn) {
|
||||
functions.emplace(name, [=, this](C& ctx, STR_ARGS strArgs) {
|
||||
std::tuple<Args...> args = {};
|
||||
parseStrArgs<std::tuple<Args...>>(strArgs, args, ctx);
|
||||
tuple_sub_t<Args, 1> args = {};
|
||||
parseStrArgs<tuple_sub_t<Args, 1>>(strArgs, args, ctx);
|
||||
return std::apply(fn, std::tuple_cat(std::tuple<C&>(ctx), args));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to add the literal function, which is called when a string literal is found.
|
||||
* This is shorthand for calling addFn<string>("_", ...).
|
||||
* The literal function must only accept a single string parameter.
|
||||
*
|
||||
* @param fn - The literal function lambda.
|
||||
*/
|
||||
|
||||
template <typename Func>
|
||||
inline void addLiteralFn(const Func& fn) {
|
||||
addFn<string>("_", 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 <typename Func>
|
||||
inline void addLiteralFnCtx(const Func& fn) {
|
||||
addFnCtx<string>("_", fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string using the functions defined.
|
||||
|
|
|
@ -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<typename T, usize N, usize M, typename... Args>
|
||||
inline constexpr auto tuple_sub_impl() {
|
||||
if constexpr (N <= M) return tuple_sub_impl<T, N + 1, M, Args..., std::tuple_element_t<N, T>>();
|
||||
else return std::tuple<Args...> {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <typename T, usize N, usize M = std::tuple_size_v<T> - 1>
|
||||
struct tuple_sub {
|
||||
|
||||
/** The value of the subsection of the tuple. */
|
||||
typedef decltype(tuple_sub_impl<T, N, M>()) value_type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shorthand for the value type of tuple_sub with the provided template arguments.
|
||||
*/
|
||||
|
||||
template <typename T, usize N, usize M = std::tuple_size_v<T> - 1>
|
||||
using tuple_sub_t = typename tuple_sub<T, N, M>::value_type;
|
|
@ -42,7 +42,7 @@ LocalPlayer::LocalPlayer(SubgamePtr game, LocalWorld& world, DimensionPtr dim, R
|
|||
|
||||
indicators->append<Gui::BoxElement>();
|
||||
|
||||
callbacks.emplace_back(renderer.window.input.events.bind(Input::CBType::MOUSE_PRESS, [&](u32 button, i32) {
|
||||
listeners.emplace_back(renderer.window.input.bind<Input::Event::MousePress>([&](u32 button) {
|
||||
if (button == GLFW_MOUSE_BUTTON_LEFT) mouseLeftClicked = true;
|
||||
else if (button == GLFW_MOUSE_BUTTON_RIGHT) mouseRightClicked = true;
|
||||
}));
|
||||
|
|
|
@ -150,6 +150,6 @@ private:
|
|||
/** The actual wield-item model, set to the currently held item. */
|
||||
DrawableEntity handItemModel;
|
||||
|
||||
vec<CallbackRef> callbacks;
|
||||
vec<ListenerRef> listeners;
|
||||
};
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
Loading…
Reference in New Issue