diff --git a/modules/app/private/application.cpp b/modules/app/private/application.cpp index c8ed190..1038001 100644 --- a/modules/app/private/application.cpp +++ b/modules/app/private/application.cpp @@ -5,11 +5,6 @@ namespace lt::app { void Application::game_loop() { - for (auto &system : m_systems) - { - system->init(); - } - while (true) { 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( std::remove(m_systems.begin(), m_systems.end(), system), @@ -42,7 +42,7 @@ void Application::register_system(Ref system) void Application::unregister_system(Ref system) { - m_systems_to_be_removed.emplace_back(std::move(system)); + m_systems_to_be_unregistered.emplace_back(std::move(system)); } } // namespace lt::app diff --git a/modules/app/public/application.hpp b/modules/app/public/application.hpp index bc92196..fa4ba7d 100644 --- a/modules/app/public/application.hpp +++ b/modules/app/public/application.hpp @@ -35,7 +35,9 @@ protected: private: std::vector> m_systems; - std::vector> m_systems_to_be_removed; + std::vector> m_systems_to_be_unregistered; + + std::vector> m_systems_to_be_registered; }; diff --git a/modules/app/public/system.hpp b/modules/app/public/system.hpp index 9a5b9a3..231fa88 100644 --- a/modules/app/public/system.hpp +++ b/modules/app/public/system.hpp @@ -17,7 +17,9 @@ public: 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; }; diff --git a/modules/debug/public/assertions.hpp b/modules/debug/public/assertions.hpp index 7c49594..9d6f195 100644 --- a/modules/debug/public/assertions.hpp +++ b/modules/debug/public/assertions.hpp @@ -1,6 +1,8 @@ #pragma once +#include #include +#include namespace lt { @@ -12,15 +14,6 @@ struct FailedAssertion: std::exception } }; -template -constexpr void ensure(Expression_T &&expression, std::format_string fmt, Args &&...args) -{ - if (!static_cast(expression)) - { - Logger::log(LogLvl::critical, fmt, std::forward(args)...); - throw ::lt::FailedAssertion(__FILE__, __LINE__); - } -} template constexpr void ensure(Expression_T &&expression, const char *message) diff --git a/modules/ecs/private/scene.cpp b/modules/ecs/private/scene.cpp index 9a58c1f..49168d5 100644 --- a/modules/ecs/private/scene.cpp +++ b/modules/ecs/private/scene.cpp @@ -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 { - // TagComponent tagComp(tag); - // entt::entity entity = entt::to_entity(m_registry, tagComp); - auto entity = Entity {}; - - m_registry.view().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 {}; } diff --git a/modules/ecs/public/scene.hpp b/modules/ecs/public/scene.hpp index 311f702..2d8bf7c 100644 --- a/modules/ecs/public/scene.hpp +++ b/modules/ecs/public/scene.hpp @@ -19,6 +19,12 @@ public: return m_registry.group(entt::get); } + template + auto view() + { + return m_registry.view(); + } + auto create_entity( const std::string &name, const TransformComponent &transform = TransformComponent() @@ -31,6 +37,7 @@ public: return m_registry; } + private: friend class Entity; diff --git a/modules/math/public/algebra.hpp b/modules/math/public/algebra.hpp index 895bd67..af2ed26 100644 --- a/modules/math/public/algebra.hpp +++ b/modules/math/public/algebra.hpp @@ -4,7 +4,6 @@ namespace lt::math { - /** * let... * a = h / w ==> for aspect ratio adjustment diff --git a/modules/mirror/private/entrypoint/mirror.cpp b/modules/mirror/private/entrypoint/mirror.cpp index 624df57..3254ce6 100644 --- a/modules/mirror/private/entrypoint/mirror.cpp +++ b/modules/mirror/private/entrypoint/mirror.cpp @@ -22,23 +22,26 @@ public: setup_window_system(); register_systems(); - m_window_system->add_event_listener([&](const surface::System::Event &event) { - const auto visitor = overloads { - [&](const lt::surface::KeyPressedEvent &event) { - std::cout << "key pressed: " << event.to_string() << std::endl; + m_window_system->add_event_listener( + m_window, + [&](const surface::SurfaceComponent::Event &event) { + const auto visitor = overloads { + [&](const lt::surface::KeyPressedEvent &event) { + std::cout << "key pressed: " << event.to_string() << std::endl; - if (event.get_key() == 81) - { - unregister_system(m_window_system); - log_inf("Quitting..."); - } - return true; - }, - [](const auto &) { return false; }, - }; + if (event.get_key() == 81) + { + unregister_system(m_window_system); + log_inf("Quitting..."); + } + return true; + }, + [](const auto &) { return false; }, + }; - return std::visit(visitor, event); - }); + return std::visit(visitor, event); + } + ); } void setup_window_system() diff --git a/modules/surface/CMakeLists.txt b/modules/surface/CMakeLists.txt index 9ec4c27..30e9f5e 100644 --- a/modules/surface/CMakeLists.txt +++ b/modules/surface/CMakeLists.txt @@ -1,5 +1,5 @@ if (NOT WIN32) - add_library_module(surface system.cpp linux/system.cpp) + add_library_module(surface linux/system.cpp) else() endif() @@ -11,3 +11,6 @@ target_link_libraries(surface PUBLIC logger lt_debug ) + +add_test_module(surface system.test.cpp) +target_link_libraries(surface_tests PRIVATE glfw) diff --git a/modules/surface/private/linux/system.cpp b/modules/surface/private/linux/system.cpp index 2e8e315..542bb9e 100644 --- a/modules/surface/private/linux/system.cpp +++ b/modules/surface/private/linux/system.cpp @@ -1,11 +1,13 @@ +#define GLFW_EXPOSE_NATIVE_X11 #include +#include #include 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 *>( + auto &callbacks = *static_cast *>( glfwGetWindowUserPointer(window) ); @@ -90,6 +92,50 @@ void bind_glfw_events(GLFWwindow *handle) }); } +System::System(Ref registry): m_registry(std::move(registry)) +{ + ensure(m_registry, "Failed to initialize surface system: null registry"); + ensure( + m_registry->view().size() == 0, + "Failed to initialize surface system: registry has surface component(s)" + ); + + m_registry->get_entt_registry() + .on_construct() + .connect<&System::on_surface_construct>(this); + + m_registry->get_entt_registry() + .on_update() + .connect<&System::on_surface_update>(this); + + m_registry->get_entt_registry() + .on_destroy() + .connect<&System::on_surface_destroy>(this); +} + +System::~System() +{ + m_registry->get_entt_registry() + .on_construct() + .disconnect<&System::on_surface_construct>(this); + + m_registry->get_entt_registry() + .on_update() + .connect<&System::on_surface_update>(this); + + m_registry->get_entt_registry() + .on_destroy() + .disconnect<&System::on_surface_destroy>(this); + + + m_registry->view().each([&](const entt::entity entity, SurfaceComponent &) { + std::cout << "REMOVED SURFACE COMPONENT ON DESTRUCTION" << std::endl; + m_registry->get_entt_registry().remove(entity); + }); + + glfwTerminate(); +} + void System::on_surface_construct(entt::registry ®istry, entt::entity entity) { ensure(glfwInit(), "Failed to initialize 'glfw'"); @@ -111,10 +157,17 @@ void System::on_surface_construct(entt::registry ®istry, entt::entity entity) ); 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); } +void System::on_surface_update(entt::registry ®istry, entt::entity entity) +{ + auto &surface = registry.get(entity); + glfwSetWindowUserPointer(surface.m_glfw_handle, &surface.m_event_callbacks); +} + void System::on_surface_destroy(entt::registry ®istry, entt::entity entity) { auto &surface = registry.get(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(); + surface.m_event_callbacks.emplace_back(std::move(callback)); +} + } // namespace lt::surface 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(event)); -// break; -// -// default: break; -// } -// } -// -// void System::on_surface_resize(const WindowResizedEvent &event) -// { -// m_properties.size = event.get_size(); -// } - } // namespace lt diff --git a/modules/surface/private/system.cpp b/modules/surface/private/system.cpp deleted file mode 100644 index 56fc395..0000000 --- a/modules/surface/private/system.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -namespace lt::surface { - -System::System(Ref registry): m_registry(std::move(registry)) -{ - m_registry->get_entt_registry() - .on_construct() - .connect<&System::on_surface_construct>(this); - - m_registry->get_entt_registry() - .on_destroy() - .connect<&System::on_surface_destroy>(this); -} - -System::~System() -{ - m_registry->get_entt_registry() - .on_construct() - .disconnect<&System::on_surface_construct>(this); - - m_registry->get_entt_registry() - .on_destroy() - .disconnect<&System::on_surface_destroy>(this); -} - -} // namespace lt::surface diff --git a/modules/surface/private/system.test.cpp b/modules/surface/private/system.test.cpp index e69de29..31cdcbd 100644 --- a/modules/surface/private/system.test.cpp +++ b/modules/surface/private/system.test.cpp @@ -0,0 +1,151 @@ +#include +#include + +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 + { + return m_registry; + } + + auto add_surface_component() -> SurfaceComponent & + { + auto entity = m_registry->create_entity(""); + return entity.add_component(SurfaceComponent::CreateInfo { + .title = title, + .size = { width, height }, + .vsync = vsync, + .visible = visible, + }); + } + + void check_values(const SurfaceComponent &component) + { + expect_ne(std::get(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 m_registry = create_ref(); +}; + +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()->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().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().size(), 0); + }; +}; + +Suite registry_events = [] { + Case { "on_construct initializes component" } = [] { + auto fixture = Fixture {}; + auto system = System { fixture.registry() }; + + const auto &component = fixture.add_surface_component(); + expect_eq(fixture.registry()->view().size(), 1); + fixture.check_values(component); + }; + + Case { "on_destrroy cleans up component" } = [] { + auto fixture = Fixture {}; + auto system = create_scope(fixture.registry()); + + const auto &component = fixture.add_surface_component(); + expect_eq(fixture.registry()->view().size(), 1); + fixture.check_values(component); + + system.reset(); + expect_eq(fixture.registry()->view().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 = [] { +}; diff --git a/modules/surface/public/components.hpp b/modules/surface/public/components.hpp index dba6b6e..798edab 100644 --- a/modules/surface/public/components.hpp +++ b/modules/surface/public/components.hpp @@ -1,16 +1,52 @@ #pragma once #include +#include +#include +#include +#include struct GLFWwindow; namespace lt::surface { +/** Represents a platform's surface (eg. a Window). + * + * @note Read-only component, should only be modified through a system. + */ class SurfaceComponent { public: 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; + + using WindowsNativeHandle = void *; + + using X11NativeHandle = unsigned long; + + using NativeHandle = std::variant; + struct CreateInfo { std::string_view title; @@ -50,17 +86,17 @@ public: return m_visible; } - [[nodiscard]] auto get_native_handle() const -> void * + [[nodiscard]] auto get_native_handle() const -> NativeHandle { return m_native_handle; } +private: [[nodiscard]] auto get_glfw_handle() const -> GLFWwindow * { return m_glfw_handle; } -private: std::string_view m_title; math::uvec2 m_size; @@ -69,9 +105,11 @@ private: bool m_visible; - void *m_native_handle {}; + NativeHandle m_native_handle; GLFWwindow *m_glfw_handle {}; + + std::vector m_event_callbacks; }; } // namespace lt::surface diff --git a/modules/surface/public/system.hpp b/modules/surface/public/system.hpp index 711a6da..b7709ab 100644 --- a/modules/surface/public/system.hpp +++ b/modules/surface/public/system.hpp @@ -4,38 +4,13 @@ #include #include #include -#include -#include -#include namespace lt::surface { class System: public app::ISystem { public: - 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; - - System(Ref registry); + [[nodiscard]] System(Ref registry); ~System() override; @@ -47,7 +22,11 @@ public: 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 add_event_listener(EventCallback callback) - { - m_event_callbacks.emplace_back(std::move(callback)); - } + void add_event_listener(ecs::Entity surface_entity, SurfaceComponent::EventCallback callback); private: void on_surface_construct(entt::registry ®istry, entt::entity entity); + void on_surface_update(entt::registry ®istry, entt::entity entity); + void on_surface_destroy(entt::registry ®istry, entt::entity entity); Ref m_registry; - - std::vector m_event_callbacks; }; diff --git a/modules/test/public/expects.hpp b/modules/test/public/expects.hpp index ef342d1..825de25 100644 --- a/modules/test/public/expects.hpp +++ b/modules/test/public/expects.hpp @@ -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( Testable auto lhs, Testable auto rhs, diff --git a/modules/test/public/test.hpp b/modules/test/public/test.hpp index ce7ec18..92f7c18 100644 --- a/modules/test/public/test.hpp +++ b/modules/test/public/test.hpp @@ -84,7 +84,8 @@ struct Case { auto operator=(std::invocable auto test) -> void // NOLINT { - std::cout << "Running... " << name; + std::cout << "[Running-----------] --> "; + std::cout << name << '\n'; try { @@ -92,14 +93,14 @@ struct Case } catch (const std::exception &exp) { - std::cout << " --> FAIL !" << '\n'; - std::cout << exp.what() << "\n\n"; + std::cout << exp.what() << "\n"; + std::cout << "[-----------FAIL !!]" << "\n\n"; details::Registry::increment_failed_count(); return; // TODO(Light): Should we run the remaining tests after a failure? } details::Registry::increment_passed_count(); - std::cout << " --> SUCCESS :D" << "\n"; + std::cout << "[--------SUCCESS :D]" << "\n\n"; } std::string_view name;