new EventEmitter template class that can have arbitrary event parameters, and is awesome

master
Auri 2021-10-11 12:46:57 -07:00
parent b7825b84ce
commit 4003a9c169
25 changed files with 399 additions and 196 deletions

View File

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

View File

@ -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) {

View File

@ -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. */

View File

@ -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() {

View File

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

View File

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

View File

@ -76,6 +76,6 @@ private:
GLint currentModelUniform;
double elapsedTime = 0;
vec<CallbackRef> callbacks {};
vec<ListenerRef> callbacks {};
};

View File

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

View File

@ -59,7 +59,7 @@ namespace Gui {
private:
Window& window;
vec<CallbackRef> callbacks {};
vec<ListenerRef> callbacks {};
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

250
src/util/EventEmitter.h Normal file
View File

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

View File

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

View File

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

42
src/util/TupleSub.h Normal file
View File

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

View File

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

View File

@ -150,6 +150,6 @@ private:
/** The actual wield-item model, set to the currently held item. */
DrawableEntity handItemModel;
vec<CallbackRef> callbacks;
vec<ListenerRef> listeners;
};

View File

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