Skip to content

Instantly share code, notes, and snippets.

@linkdd
Last active April 30, 2023 22:53
Show Gist options
  • Save linkdd/efb78d449b8497d0d4541090eae3d550 to your computer and use it in GitHub Desktop.
Save linkdd/efb78d449b8497d0d4541090eae3d550 to your computer and use it in GitHub Desktop.
EnTT + ImGui framework similar to React
#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 &registry, hooks &h) = 0;
public:
void frame_begin() {
m_hooks.reset();
}
void frame_update(entt::registry &registry) {
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