refactor: surface, app, tests, ecs refactors
Some checks reported errors
continuous-integration/drone/push Build was killed

This commit is contained in:
light7734 2025-07-28 20:45:24 +03:30
parent a102db0699
commit 638a009047
Signed by: light7734
GPG key ID: 8C30176798F1A6BA
16 changed files with 336 additions and 141 deletions

View file

@ -5,11 +5,6 @@ namespace lt::app {
void Application::game_loop() void Application::game_loop()
{ {
for (auto &system : m_systems)
{
system->init();
}
while (true) while (true)
{ {
for (auto &system : m_systems) for (auto &system : m_systems)
@ -20,7 +15,12 @@ void Application::game_loop()
} }
} }
for (auto &system : m_systems_to_be_removed) for (auto &system : m_systems_to_be_registered)
{
m_systems.emplace_back(system)->on_register();
}
for (auto &system : m_systems_to_be_unregistered)
{ {
m_systems.erase( m_systems.erase(
std::remove(m_systems.begin(), m_systems.end(), system), std::remove(m_systems.begin(), m_systems.end(), system),
@ -42,7 +42,7 @@ void Application::register_system(Ref<app::ISystem> system)
void Application::unregister_system(Ref<app::ISystem> system) void Application::unregister_system(Ref<app::ISystem> system)
{ {
m_systems_to_be_removed.emplace_back(std::move(system)); m_systems_to_be_unregistered.emplace_back(std::move(system));
} }
} // namespace lt::app } // namespace lt::app

View file

@ -35,7 +35,9 @@ protected:
private: private:
std::vector<Ref<app::ISystem>> m_systems; std::vector<Ref<app::ISystem>> m_systems;
std::vector<Ref<app::ISystem>> m_systems_to_be_removed; std::vector<Ref<app::ISystem>> m_systems_to_be_unregistered;
std::vector<Ref<app::ISystem>> m_systems_to_be_registered;
}; };

View file

@ -17,7 +17,9 @@ public:
auto operator=(const ISystem &) -> ISystem & = delete; auto operator=(const ISystem &) -> ISystem & = delete;
virtual void init() = 0; virtual void on_register() = 0;
virtual void on_unregister() = 0;
virtual auto tick() -> bool = 0; virtual auto tick() -> bool = 0;
}; };

View file

@ -1,6 +1,8 @@
#pragma once #pragma once
#include <format>
#include <logger/logger.hpp> #include <logger/logger.hpp>
#include <source_location>
namespace lt { namespace lt {
@ -12,15 +14,6 @@ struct FailedAssertion: std::exception
} }
}; };
template<typename Expression_T, typename... Args>
constexpr void ensure(Expression_T &&expression, std::format_string<Args...> fmt, Args &&...args)
{
if (!static_cast<bool>(expression))
{
Logger::log(LogLvl::critical, fmt, std::forward<Args>(args)...);
throw ::lt::FailedAssertion(__FILE__, __LINE__);
}
}
template<typename Expression_T> template<typename Expression_T>
constexpr void ensure(Expression_T &&expression, const char *message) constexpr void ensure(Expression_T &&expression, const char *message)

View file

@ -11,21 +11,6 @@ auto Scene::create_entity(const std::string &name, const TransformComponent &tra
auto Scene::get_entity_by_tag(const std::string &tag) -> Entity auto Scene::get_entity_by_tag(const std::string &tag) -> Entity
{ {
// TagComponent tagComp(tag);
// entt::entity entity = entt::to_entity(m_registry, tagComp);
auto entity = Entity {};
m_registry.view<TagComponent>().each([&](TagComponent &tagComp) {
// if (tagComp.tag == tag)
// entity = entity(entt::to_entity(m_registry, tagComp), this);
});
if (entity.is_valid())
{
return entity;
}
ensure(false, "Scene::get_entity_by_tag: failed to find entity by tag: {}", tag);
return {}; return {};
} }

View file

@ -19,6 +19,12 @@ public:
return m_registry.group(entt::get<T...>); return m_registry.group(entt::get<T...>);
} }
template<typename T>
auto view()
{
return m_registry.view<T>();
}
auto create_entity( auto create_entity(
const std::string &name, const std::string &name,
const TransformComponent &transform = TransformComponent() const TransformComponent &transform = TransformComponent()
@ -31,6 +37,7 @@ public:
return m_registry; return m_registry;
} }
private: private:
friend class Entity; friend class Entity;

View file

@ -4,7 +4,6 @@
namespace lt::math { namespace lt::math {
/** /**
* let... * let...
* a = h / w ==> for aspect ratio adjustment * a = h / w ==> for aspect ratio adjustment

View file

@ -22,7 +22,9 @@ public:
setup_window_system(); setup_window_system();
register_systems(); register_systems();
m_window_system->add_event_listener([&](const surface::System::Event &event) { m_window_system->add_event_listener(
m_window,
[&](const surface::SurfaceComponent::Event &event) {
const auto visitor = overloads { const auto visitor = overloads {
[&](const lt::surface::KeyPressedEvent &event) { [&](const lt::surface::KeyPressedEvent &event) {
std::cout << "key pressed: " << event.to_string() << std::endl; std::cout << "key pressed: " << event.to_string() << std::endl;
@ -38,7 +40,8 @@ public:
}; };
return std::visit(visitor, event); return std::visit(visitor, event);
}); }
);
} }
void setup_window_system() void setup_window_system()

View file

@ -1,5 +1,5 @@
if (NOT WIN32) if (NOT WIN32)
add_library_module(surface system.cpp linux/system.cpp) add_library_module(surface linux/system.cpp)
else() else()
endif() endif()
@ -11,3 +11,6 @@ target_link_libraries(surface PUBLIC
logger logger
lt_debug lt_debug
) )
add_test_module(surface system.test.cpp)
target_link_libraries(surface_tests PRIVATE glfw)

View file

@ -1,11 +1,13 @@
#define GLFW_EXPOSE_NATIVE_X11
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <surface/system.hpp> #include <surface/system.hpp>
namespace lt::surface { namespace lt::surface {
void handle_event(GLFWwindow *window, const System::Event &event) void handle_event(GLFWwindow *window, const SurfaceComponent::Event &event)
{ {
auto &callbacks = *static_cast<std::vector<System::EventCallback> *>( auto &callbacks = *static_cast<std::vector<SurfaceComponent::EventCallback> *>(
glfwGetWindowUserPointer(window) glfwGetWindowUserPointer(window)
); );
@ -90,6 +92,50 @@ void bind_glfw_events(GLFWwindow *handle)
}); });
} }
System::System(Ref<ecs::Registry> registry): m_registry(std::move(registry))
{
ensure(m_registry, "Failed to initialize surface system: null registry");
ensure(
m_registry->view<SurfaceComponent>().size() == 0,
"Failed to initialize surface system: registry has surface component(s)"
);
m_registry->get_entt_registry()
.on_construct<SurfaceComponent>()
.connect<&System::on_surface_construct>(this);
m_registry->get_entt_registry()
.on_update<SurfaceComponent>()
.connect<&System::on_surface_update>(this);
m_registry->get_entt_registry()
.on_destroy<SurfaceComponent>()
.connect<&System::on_surface_destroy>(this);
}
System::~System()
{
m_registry->get_entt_registry()
.on_construct<SurfaceComponent>()
.disconnect<&System::on_surface_construct>(this);
m_registry->get_entt_registry()
.on_update<SurfaceComponent>()
.connect<&System::on_surface_update>(this);
m_registry->get_entt_registry()
.on_destroy<SurfaceComponent>()
.disconnect<&System::on_surface_destroy>(this);
m_registry->view<SurfaceComponent>().each([&](const entt::entity entity, SurfaceComponent &) {
std::cout << "REMOVED SURFACE COMPONENT ON DESTRUCTION" << std::endl;
m_registry->get_entt_registry().remove<SurfaceComponent>(entity);
});
glfwTerminate();
}
void System::on_surface_construct(entt::registry &registry, entt::entity entity) void System::on_surface_construct(entt::registry &registry, entt::entity entity)
{ {
ensure(glfwInit(), "Failed to initialize 'glfw'"); ensure(glfwInit(), "Failed to initialize 'glfw'");
@ -111,10 +157,17 @@ void System::on_surface_construct(entt::registry &registry, entt::entity entity)
); );
ensure(surface.m_glfw_handle, "Failed to create 'GLFWwindow'"); ensure(surface.m_glfw_handle, "Failed to create 'GLFWwindow'");
glfwSetWindowUserPointer(surface.m_glfw_handle, &m_event_callbacks); glfwSetWindowUserPointer(surface.m_glfw_handle, &surface.m_event_callbacks);
surface.m_native_handle = glfwGetX11Window(surface.m_glfw_handle);
bind_glfw_events(surface.m_glfw_handle); bind_glfw_events(surface.m_glfw_handle);
} }
void System::on_surface_update(entt::registry &registry, entt::entity entity)
{
auto &surface = registry.get<SurfaceComponent>(entity);
glfwSetWindowUserPointer(surface.m_glfw_handle, &surface.m_event_callbacks);
}
void System::on_surface_destroy(entt::registry &registry, entt::entity entity) void System::on_surface_destroy(entt::registry &registry, entt::entity entity)
{ {
auto &surface = registry.get<SurfaceComponent>(entity); auto &surface = registry.get<SurfaceComponent>(entity);
@ -170,29 +223,17 @@ void System::set_visibility(ecs::Entity surface_entity, bool visible)
} }
} }
void System::add_event_listener(
ecs::Entity surface_entity,
SurfaceComponent::EventCallback callback
)
{
auto &surface = surface_entity.get_component<SurfaceComponent>();
surface.m_event_callbacks.emplace_back(std::move(callback));
}
} // namespace lt::surface } // namespace lt::surface
namespace lt { namespace lt {
// void System::on_event(const Event &event)
// {
// switch (event.get_event_type())
// {
// /* closed */
// case EventType::WindowClosed: b_Closed = true; break;
//
// /* resized */
// case EventType::WindowResized:
// on_surface_resize(dynamic_cast<const WindowResizedEvent &>(event));
// break;
//
// default: break;
// }
// }
//
// void System::on_surface_resize(const WindowResizedEvent &event)
// {
// m_properties.size = event.get_size();
// }
} // namespace lt } // namespace lt

View file

@ -1,27 +0,0 @@
#include <surface/system.hpp>
namespace lt::surface {
System::System(Ref<ecs::Registry> registry): m_registry(std::move(registry))
{
m_registry->get_entt_registry()
.on_construct<SurfaceComponent>()
.connect<&System::on_surface_construct>(this);
m_registry->get_entt_registry()
.on_destroy<SurfaceComponent>()
.connect<&System::on_surface_destroy>(this);
}
System::~System()
{
m_registry->get_entt_registry()
.on_construct<SurfaceComponent>()
.disconnect<&System::on_surface_construct>(this);
m_registry->get_entt_registry()
.on_destroy<SurfaceComponent>()
.disconnect<&System::on_surface_destroy>(this);
}
} // namespace lt::surface

View file

@ -0,0 +1,151 @@
#include <surface/system.hpp>
#include <test/test.hpp>
using namespace lt;
using std::ignore;
using surface::SurfaceComponent;
using surface::System;
using test::Case;
using test::expect_eq;
using test::expect_ne;
using test::expect_not_nullptr;
using test::expect_throw;
using test::Suite;
constexpr auto title = "TestWindow";
constexpr auto width = 800u;
constexpr auto height = 600u;
constexpr auto vsync = true;
constexpr auto visible = false;
class Fixture
{
public:
[[nodiscard]] auto registry() -> Ref<ecs::Registry>
{
return m_registry;
}
auto add_surface_component() -> SurfaceComponent &
{
auto entity = m_registry->create_entity("");
return entity.add_component<SurfaceComponent>(SurfaceComponent::CreateInfo {
.title = title,
.size = { width, height },
.vsync = vsync,
.visible = visible,
});
}
void check_values(const SurfaceComponent &component)
{
expect_ne(std::get<SurfaceComponent::X11NativeHandle>(component.get_native_handle()), 0);
expect_eq(component.get_size().x, width);
expect_eq(component.get_size().y, height);
expect_eq(component.get_title(), title);
expect_eq(component.is_vsync(), vsync);
expect_eq(component.is_visible(), visible);
}
private:
Ref<ecs::Registry> m_registry = create_ref<ecs::Registry>();
};
Suite raii = [] {
Case { "happy path won't throw" } = [] {
auto fixture = Fixture {};
ignore = System { fixture.registry() };
};
Case { "many won't throw" } = [] {
auto fixture = Fixture {};
for (auto idx : std::views::iota(0, 100'001))
{
ignore = System { fixture.registry() };
}
};
Case { "unhappy path throws" } = [] {
expect_throw([] { ignore = System { {} }; });
auto fixture = Fixture {};
fixture.add_surface_component();
expect_throw([&] { ignore = System { { fixture.registry() } }; });
};
Case { "post construct has correct state" } = [] {
auto fixture = Fixture {};
auto system = System { fixture.registry() };
expect_eq(fixture.registry()->view<SurfaceComponent>()->size(), 0);
};
};
Suite system_events = [] {
Case { "on_register won't throw" } = [] {
auto fixture = Fixture {};
auto system = System { fixture.registry() };
system.on_register();
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 0);
};
Case { "on_unregister won't throw" } = [] {
auto fixture = Fixture {};
auto system = System { fixture.registry() };
system.on_register();
system.on_unregister();
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 0);
};
};
Suite registry_events = [] {
Case { "on_construct<SurfaceComponent> initializes component" } = [] {
auto fixture = Fixture {};
auto system = System { fixture.registry() };
const auto &component = fixture.add_surface_component();
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 1);
fixture.check_values(component);
};
Case { "on_destrroy<SurfaceComponent> cleans up component" } = [] {
auto fixture = Fixture {};
auto system = create_scope<System>(fixture.registry());
const auto &component = fixture.add_surface_component();
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 1);
fixture.check_values(component);
system.reset();
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 0);
};
};
Suite tick = [] {
Case { "ticking on empty registry won't throw" } = [] {
auto fixture = Fixture {};
System { fixture.registry() }.tick();
};
Case { "ticking on non-empty registry won't throw" } = [] {
auto fixture = Fixture {};
auto system = System { fixture.registry() };
fixture.add_surface_component();
system.tick();
};
Case { "ticking on chaotic registry won't throw" } = [] {
}
};
Suite property_setters = [] {
};
Suite listeners = [] {
};
Suite fuzzy = [] {
};

View file

@ -1,16 +1,52 @@
#pragma once #pragma once
#include <math/vec2.hpp> #include <math/vec2.hpp>
#include <surface/events/keyboard.hpp>
#include <surface/events/mouse.hpp>
#include <surface/events/surface.hpp>
#include <variant>
struct GLFWwindow; struct GLFWwindow;
namespace lt::surface { namespace lt::surface {
/** Represents a platform's surface (eg. a Window).
*
* @note Read-only component, should only be modified through a system.
*/
class SurfaceComponent class SurfaceComponent
{ {
public: public:
friend class System; friend class System;
using Event = std::variant<
// surface events
ClosedEvent,
MovedEvent,
ResizedEvent,
LostFocusEvent,
GainFocusEvent,
// keyboard events
KeyPressedEvent,
KeyRepeatEvent,
KeyReleasedEvent,
KeySetCharEvent,
// mouse events
MouseMovedEvent,
WheelScrolledEvent,
ButtonPressedEvent,
ButtonReleasedEvent>;
using EventCallback = std::function<bool(const Event &)>;
using WindowsNativeHandle = void *;
using X11NativeHandle = unsigned long;
using NativeHandle = std::variant<WindowsNativeHandle, X11NativeHandle>;
struct CreateInfo struct CreateInfo
{ {
std::string_view title; std::string_view title;
@ -50,17 +86,17 @@ public:
return m_visible; return m_visible;
} }
[[nodiscard]] auto get_native_handle() const -> void * [[nodiscard]] auto get_native_handle() const -> NativeHandle
{ {
return m_native_handle; return m_native_handle;
} }
private:
[[nodiscard]] auto get_glfw_handle() const -> GLFWwindow * [[nodiscard]] auto get_glfw_handle() const -> GLFWwindow *
{ {
return m_glfw_handle; return m_glfw_handle;
} }
private:
std::string_view m_title; std::string_view m_title;
math::uvec2 m_size; math::uvec2 m_size;
@ -69,9 +105,11 @@ private:
bool m_visible; bool m_visible;
void *m_native_handle {}; NativeHandle m_native_handle;
GLFWwindow *m_glfw_handle {}; GLFWwindow *m_glfw_handle {};
std::vector<EventCallback> m_event_callbacks;
}; };
} // namespace lt::surface } // namespace lt::surface

View file

@ -4,38 +4,13 @@
#include <ecs/entity.hpp> #include <ecs/entity.hpp>
#include <ecs/scene.hpp> #include <ecs/scene.hpp>
#include <surface/components.hpp> #include <surface/components.hpp>
#include <surface/events/keyboard.hpp>
#include <surface/events/mouse.hpp>
#include <surface/events/surface.hpp>
namespace lt::surface { namespace lt::surface {
class System: public app::ISystem class System: public app::ISystem
{ {
public: public:
using Event = std::variant< [[nodiscard]] System(Ref<ecs::Registry> registry);
// surface events
ClosedEvent,
MovedEvent,
ResizedEvent,
LostFocusEvent,
GainFocusEvent,
// keyboard events
KeyPressedEvent,
KeyRepeatEvent,
KeyReleasedEvent,
KeySetCharEvent,
// mouse events
MouseMovedEvent,
WheelScrolledEvent,
ButtonPressedEvent,
ButtonReleasedEvent>;
using EventCallback = std::function<bool(const Event &)>;
System(Ref<ecs::Registry> registry);
~System() override; ~System() override;
@ -47,7 +22,11 @@ public:
auto operator=(const System &) -> System & = delete; auto operator=(const System &) -> System & = delete;
void init() override void on_register() override
{
}
void on_unregister() override
{ {
} }
@ -61,19 +40,16 @@ public:
void set_visibility(ecs::Entity surface_entity, bool visible); void set_visibility(ecs::Entity surface_entity, bool visible);
void add_event_listener(EventCallback callback) void add_event_listener(ecs::Entity surface_entity, SurfaceComponent::EventCallback callback);
{
m_event_callbacks.emplace_back(std::move(callback));
}
private: private:
void on_surface_construct(entt::registry &registry, entt::entity entity); void on_surface_construct(entt::registry &registry, entt::entity entity);
void on_surface_update(entt::registry &registry, entt::entity entity);
void on_surface_destroy(entt::registry &registry, entt::entity entity); void on_surface_destroy(entt::registry &registry, entt::entity entity);
Ref<ecs::Registry> m_registry; Ref<ecs::Registry> m_registry;
std::vector<EventCallback> m_event_callbacks;
}; };

View file

@ -140,6 +140,27 @@ constexpr void expect_false(
} }
} }
constexpr void expect_not_nullptr(
auto *pointer,
std::source_location source_location = std::source_location::current()
)
{
if (pointer == nullptr)
{
throw std::runtime_error {
std::format(
"Failed true expectation:\n"
"\tactual: nullptr\n"
"\texpected: not nullptr\n"
"\tlocation: {}:{}",
source_location.file_name(),
source_location.line()
),
};
}
}
constexpr void expect_le( constexpr void expect_le(
Testable auto lhs, Testable auto lhs,
Testable auto rhs, Testable auto rhs,

View file

@ -84,7 +84,8 @@ struct Case
{ {
auto operator=(std::invocable auto test) -> void // NOLINT auto operator=(std::invocable auto test) -> void // NOLINT
{ {
std::cout << "Running... " << name; std::cout << "[Running-----------] --> ";
std::cout << name << '\n';
try try
{ {
@ -92,14 +93,14 @@ struct Case
} }
catch (const std::exception &exp) catch (const std::exception &exp)
{ {
std::cout << " --> FAIL !" << '\n'; std::cout << exp.what() << "\n";
std::cout << exp.what() << "\n\n"; std::cout << "[-----------FAIL !!]" << "\n\n";
details::Registry::increment_failed_count(); details::Registry::increment_failed_count();
return; // TODO(Light): Should we run the remaining tests after a failure? return; // TODO(Light): Should we run the remaining tests after a failure?
} }
details::Registry::increment_passed_count(); details::Registry::increment_passed_count();
std::cout << " --> SUCCESS :D" << "\n"; std::cout << "[--------SUCCESS :D]" << "\n\n";
} }
std::string_view name; std::string_view name;