Last active
April 30, 2023 22:53
-
-
Save linkdd/efb78d449b8497d0d4541090eae3d550 to your computer and use it in GitHub Desktop.
EnTT + ImGui framework similar to React
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <type_traits> | |
#include <concepts> | |
#include <functional> | |
#include <stdexcept> | |
#include <optional> | |
#include <vector> | |
#include <tuple> | |
#include <entt/entt.hpp> | |
namespace tw::gui { | |
namespace details { | |
template <typename T> | |
struct is_callback_fn : std::false_type {}; | |
template <typename R, typename... Args> | |
struct is_callback_fn<std::function<R(Args...)>> : std::true_type {}; | |
}; | |
template <typename T> | |
using setter_fn_type = std::function<void(const T&)>; | |
template <typename T> | |
using state_pair_type = std::pair<const T&, setter_fn_type<T>>; | |
using cleanup_fn_type = std::function<void()>; | |
using effect_fn_type = std::function<std::optional<cleanup_fn_type>()>; | |
template <typename T> | |
concept is_callback_fn = details::is_callback_fn<T>::value; | |
class hooks { | |
friend class component; | |
private: | |
using mutation_type = std::function<void()>; | |
private: | |
entt::id_type m_current_index{0}; | |
entt::registry::context m_state; | |
std::vector<mutation_type> m_mutation_queue; | |
private: | |
void reset() { | |
m_current_index = 0; | |
} | |
void commit() { | |
for (const auto &mutation : m_mutation_queue) { | |
mutation(); | |
} | |
m_mutation_queue.clear(); | |
} | |
public: | |
template <typename T> | |
state_pair_type<T> use_state(const T& initial_value) { | |
entt::id_type key = m_current_index++; | |
if (!m_state.contains<T>(key)) { | |
m_state.emplace_as<T>(key, initial_value); | |
} | |
const T& value = m_state.get<T>(key); | |
auto update_fn = [this, key](const T& new_value) -> void { | |
m_mutation_queue.push_back([this, key, new_value]() -> void { | |
m_state.erase<T>(key); | |
m_state.emplace_as<T>(key, new_value); | |
}); | |
}; | |
return {value, update_fn}; | |
} | |
template <typename... Deps> | |
void use_effect(effect_fn_type effect_fn, std::tuple<Deps...> deps) { | |
entt::id_type effect_key = m_current_index++; | |
entt::id_type deps_key = m_current_index++; | |
if (!m_state.contains<std::tuple<Deps...>>(deps_key)) { | |
m_state.emplace_as<std::tuple<Deps...>>(deps_key, deps); | |
auto cleanup_fn = effect_fn(); | |
if (cleanup_fn) { | |
if (m_state.contains<cleanup_fn_type>(effect_key)) { | |
m_state.erase<cleanup_fn_type>(effect_key); | |
} | |
m_state.emplace_as<cleanup_fn_type>(effect_key, *cleanup_fn); | |
} | |
} | |
else { | |
const auto& old_deps = m_state.get<std::tuple<Deps...>>(deps_key); | |
if (deps != old_deps) { | |
if (m_state.contains<cleanup_fn_type>(effect_key)) { | |
m_state.get<cleanup_fn_type>(effect_key)(); | |
m_state.erase<cleanup_fn_type>(effect_key); | |
} | |
m_state.erase<std::tuple<Deps...>>(deps_key); | |
m_state.emplace_as<std::tuple<Deps...>>(deps_key, deps); | |
auto cleanup_fn = effect_fn(); | |
if (cleanup_fn) { | |
if (m_state.contains<cleanup_fn_type>(effect_key)) { | |
m_state.erase<cleanup_fn_type>(effect_key); | |
} | |
m_state.emplace_as<cleanup_fn_type>(effect_key, *cleanup_fn); | |
} | |
} | |
} | |
} | |
template <is_callback_fn F, typename... Deps> | |
F use_callback(F&& callback_fn, std::tuple<Deps...> deps) { | |
entt::id_type cb_key = m_current_index++; | |
entt::id_type deps_key = m_current_index++; | |
if (!m_state.contains<std::tuple<Deps...>>(deps_key)) { | |
m_state.emplace_as<std::tuple<Deps...>>(deps_key, deps); | |
if (m_state.contains<F>(cb_key)) { | |
m_state.erase<F>(cb_key); | |
} | |
m_state.emplace_as<F>(cb_key, callback_fn); | |
} | |
else { | |
const auto& old_deps = m_state.get<std::tuple<Deps...>>(deps_key); | |
if (deps != old_deps) { | |
m_state.erase<std::tuple<Deps...>>(deps_key); | |
m_state.emplace_as<std::tuple<Deps...>>(deps_key, deps); | |
if (m_state.contains<F>(cb_key)) { | |
m_state.erase<F>(cb_key); | |
} | |
m_state.emplace_as<F>(cb_key, callback_fn); | |
} | |
} | |
return m_state.get<F>(cb_key); | |
} | |
}; | |
class component { | |
private: | |
hooks m_hooks; | |
public: | |
virtual void render(entt::registry ®istry, hooks &h) = 0; | |
public: | |
void frame_begin() { | |
m_hooks.reset(); | |
} | |
void frame_update(entt::registry ®istry) { | |
render(registry, m_hooks); | |
} | |
void frame_end() { | |
m_hooks.commit(); | |
} | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment