diff --git a/include/fennec/core/event.h b/include/fennec/core/event.h index e44a648..2d50a56 100644 --- a/include/fennec/core/event.h +++ b/include/fennec/core/event.h @@ -39,6 +39,11 @@ public: /// \brief event handler callback /// \param event the event to handle virtual void handle_event(event* event) = 0; + +#ifndef FENNEC_DOXYGEN + FENNEC_RTTI_CLASS_ENABLE() { + } +#endif }; /// @@ -46,36 +51,38 @@ public: struct event { virtual ~event() = default; + static void handle_events(); + /// /// \brief Registers a listener for the event type /// \tparam EventT the event type /// \param listener the listener to register template static void add_listener(event_listener* listener) { - event::add_listener(listener, typeuuid()); + event::_add_listener(listener, type::get().id()); } - /// - /// \brief Add a listener for an event type of \f$type\f$ - /// \param listener the listener to add - /// \param type the event type to listen for - static void add_listener(event_listener* listener, uint64_t type); - /// /// \brief removes a listener from the event system /// \param listener the listener to remove static void remove_listener(event_listener* listener); template - static void dispatch_immediate(ArgsT&&...args) { - + static void dispatch(ArgsT&&...args) { + event::_dispatch(fennec::make_unique(fennec::forward(args)...)); } template - static void dispatch_next_tick(ArgsT&&...args) { - + static void dispatch_immediate(ArgsT&&...args) { + event::_dispatch_immediate(fennec::make_unique(fennec::forward(args)...)); } +private: + static void _add_listener(event_listener* listener, uint64_t type); + static void _handle_event(unique_ptr& event); + static void _dispatch(unique_ptr&& event); + static void _dispatch_immediate(unique_ptr&& event); + #ifndef FENNEC_DOXYGEN FENNEC_RTTI_CLASS_ENABLE() { } diff --git a/include/fennec/lang/assert.h b/include/fennec/lang/assert.h index 5152d61..2fa4c00 100644 --- a/include/fennec/lang/assert.h +++ b/include/fennec/lang/assert.h @@ -31,6 +31,8 @@ #ifndef FENNEC_LANG_ASSERT_H #define FENNEC_LANG_ASSERT_H +#include + /// /// \page fennec_lang_assert Assertions /// @@ -69,7 +71,19 @@ #endif #ifndef FENNEC_DOXYGEN -void _assert_impl(const char* expression, const char* file, int line, const char* function, const char* desc, bool halt); +void _assert_impl(const char* expr, size_t expr_l, + const char* file, size_t file_l, int line, + const char* func, size_t func_l, + const char* desc, bool halt); + +template +void _assert(const char (&expr)[ExprL], + const char (&file)[FileL], int line, + const char (&func)[FuncL], + const char* desc, + bool halt) { + ::_assert_impl(expr, ExprL, file, FileL, line, func, FuncL, desc, halt); +} #endif /// @@ -78,7 +92,7 @@ void _assert_impl(const char* expression, const char* file, int line, const char /// \param description the description of the assertion #define assert(expression, description) \ if(not(expression)) [[unlikely]] { \ - _assert_impl(#expression, __FILE__, __LINE__, __PRETTY_FUNCTION__, description, not FENNEC_RELEASE); \ + _assert(#expression, __FILE__, __LINE__, __PRETTY_FUNCTION__, description, not FENNEC_RELEASE); \ } /// @@ -87,7 +101,7 @@ void _assert_impl(const char* expression, const char* file, int line, const char /// \param description the description of the assertion #define assertf(expression, description) \ if(not(expression)) [[unlikely]] { \ - _assert_impl(#expression, __FILE__, __LINE__, __PRETTY_FUNCTION__, description, true); \ + _assert(#expression, __FILE__, __LINE__, __PRETTY_FUNCTION__, description, true); \ } /// diff --git a/include/fennec/memory/pointers.h b/include/fennec/memory/pointers.h index 20137b9..3286c8d 100644 --- a/include/fennec/memory/pointers.h +++ b/include/fennec/memory/pointers.h @@ -117,13 +117,20 @@ public: } /// - /// \brief Move Constructor, transfers ownership from \f$other\f$ /// \param other The unique_ptr to take ownership from constexpr unique_ptr(unique_ptr&& other) : _handle(other._handle) { other._handle = nullptr; } + /// + /// \brief Move Constructor, transfers ownership from \f$other\f$ + /// \param other The unique_ptr to take ownership from + template requires(is_base_of_v) + constexpr unique_ptr(unique_ptr&& other) + : _handle(other.release()) { + } + // Delete copy constructor constexpr unique_ptr(const unique_ptr&) = delete; @@ -225,6 +232,11 @@ unique_ptr make_unique(ArgsT&&...args) { return unique_ptr(new TypeT(fennec::forward(args)...)); } +template +unique_ptr make_unique(TypeT* ptr) { + return unique_ptr(ptr); +} + } #endif // FENNEC_MEMORY_POINTERS_H diff --git a/include/fennec/string/cstring.h b/include/fennec/string/cstring.h index 3e0879a..4d99f91 100644 --- a/include/fennec/string/cstring.h +++ b/include/fennec/string/cstring.h @@ -81,9 +81,10 @@ public: } /// - /// \brief Buffer Constructor, wraps the provided C-Style string /// \param str the buffer to wrap /// \param n the number of characters in the buffer plus the null terminator + /// + /// \note If used with `::strlen`, the result should be incremented by 1 to include the null terminator constexpr cstring(char* str, size_t n) : _str(str) , _size(n - 1) @@ -94,7 +95,6 @@ public: } /// - /// \brief Buffer Constructor, wraps the provided C-Style string /// \param str the buffer to wrap /// \tparam n the number of characters in the buffer plus the null terminator template @@ -108,9 +108,10 @@ public: } /// - /// \brief Const Buffer Constructor, wraps the provided C-Style string /// \param str the buffer to wrap /// \param n the number of characters in the buffer plus the null terminator + /// + /// \note If used with `::strlen`, the result should be incremented by 1 to include the null terminator constexpr cstring(const char* str, size_t n) : _cstr(str) , _size(n - 1) diff --git a/source/core/event.cpp b/source/core/event.cpp index f3f63e4..1a68a31 100644 --- a/source/core/event.cpp +++ b/source/core/event.cpp @@ -19,14 +19,41 @@ #include #include #include +#include #include +#include + +#define FENNEC_EVENT_QUEUE_SIZE 8192 namespace fennec { +static mutex lock; static dynarray> listeners; +static mpscq queue(FENNEC_EVENT_QUEUE_SIZE); + +void event::handle_events() { + lock_guard guard(lock); + + // if we don't copy into a temporary buffer, we could deadlock ourselves + // if someone unwittingly calls dispatch() in an event listener. + dynarray> dispatch(FENNEC_EVENT_QUEUE_SIZE); + + // we query the size instead of empty just in case someone decides to run their own thread + // and send events from that thread. + size_t n = queue.size(); + for (size_t i = 0; i < n; ++i) { + dispatch[i] = queue.pop(); + } + + for (size_t i = 0; i < n; ++i) { + _handle_event(dispatch[i]); + } +} + +void event::_add_listener(event_listener* listener, uint64_t type) { + lock_guard guard(lock); -void event::add_listener(event_listener* listener, uint64_t type) { if (listeners.size() <= type) { listeners.resize(type + 1); } @@ -34,9 +61,29 @@ void event::add_listener(event_listener* listener, uint64_t type) { } void event::remove_listener(event_listener* listener) { + lock_guard guard(lock); + for (auto& it : listeners) { it.erase(listener); } } +void event::_handle_event(unique_ptr& event) { + auto type = event->get_type().id(); + for (auto& listener : listeners[type]) { + listener->handle_event(event.get()); + } +} + +void event::_dispatch(unique_ptr&& event) { + // queue is taking ownership of the resource + queue.emplace(event.release()); +} + +void event::_dispatch_immediate(unique_ptr&& event) { + lock_guard guard(lock); + + _handle_event(event); +} + } diff --git a/source/debug/assert_impl.cpp b/source/debug/assert_impl.cpp index 9c924d6..0a436b4 100644 --- a/source/debug/assert_impl.cpp +++ b/source/debug/assert_impl.cpp @@ -16,17 +16,24 @@ // along with this program. If not, see . // ===================================================================================================================== -#include +#include #include -void _assert_callback(const char* expression, const char* file, int line, const char* function, const char* description) +void _assert_callback(const fennec::cstring& expr, const fennec::cstring& file, int line, const fennec::cstring& func, const fennec::cstring& desc) { // Skip // __assert_callback // __assert_impl - printf("Assert failed: \"%s\" \n" + fennec::logger::log(fennec::format("" + "Assert failed: \"{}\" \n" + "At {}:{} in {} \n" + "Description: {} \n" + "Stacktrace: \n", + expr, file, line, func, desc + )); + /*printf("Assert failed: \"%s\" \n" "At %s:%d in %s \n" "Description: %s \n", - expression, file, line, function, description); + expression, file, line, function, description);*/ cpptrace::generate_trace(2).print(); -} \ No newline at end of file +} diff --git a/source/lang/assert.cpp b/source/lang/assert.cpp index dd5f51f..977f35f 100644 --- a/source/lang/assert.cpp +++ b/source/lang/assert.cpp @@ -17,12 +17,21 @@ // ===================================================================================================================== #include +#include -void _assert_callback(const char* expression, const char* file, int line, const char* function, const char* description); +void _assert_callback(const fennec::cstring& expr, const fennec::cstring& file, int line, const fennec::cstring& func, const fennec::cstring& desc); -void _assert_impl(const char* expression, const char* file, int line, const char* function, const char* description, bool halt) +void _assert_impl(const char* expr, size_t expr_l, + const char* file, size_t file_l, int line, + const char* func, size_t func_l, + const char* desc, bool halt) { - _assert_callback(expression, file, line, function, description); + _assert_callback( + fennec::cstring(expr, expr_l), + fennec::cstring(file, file_l), line, + fennec::cstring(func, func_l), + fennec::cstring(desc, ::strlen(desc) + 1) + ); if (halt) { ::abort(); diff --git a/source/platform/window_manager.cpp b/source/platform/window_manager.cpp index 8af9e14..a3ab0aa 100644 --- a/source/platform/window_manager.cpp +++ b/source/platform/window_manager.cpp @@ -70,6 +70,7 @@ void window_manager::shutdown() { } assertf(_thread == thread::current(), "Attempted to shutdown Window Manager on a different thread!"); + lock_guard guard(_lock); // Cleanup Windows for (auto& window : _windows) { @@ -84,31 +85,47 @@ void window_manager::shutdown() { } void window_manager::dispatch() { - assertf(_thread == thread::current(), "Attempted to dispatch Window Manager on a different thread!"); + if (not _display) { + return; + } + assertf(_thread == thread::current(), "Attempted to dispatch Window Manager on a different thread!"); lock_guard guard(_lock); + _display->dispatch(); } window_manager::window_id window_manager::create_window(const window::config& config, window_id parent) { - assertf(_thread == thread::current(), "Attempted to create a window on a different thread!"); + if (not _display) { + return nullid; + } + assertf(_thread == thread::current(), "Attempted to create a window on a different thread!"); lock_guard guard(_lock); + window* p = parent == nullid ? nullptr : _windows[parent].get(); return _windows.emplace(_display->create_window(config, p)); } void window_manager::begin(window_id window) { - assertf(_thread == thread::current(), "Attempted to set window context on a different thread!"); + if (not _display) { + return; + } + assertf(_thread == thread::current(), "Attempted to set window context on a different thread!"); lock_guard guard(_lock); + _windows[window]->begin_frame(); } void window_manager::end(window_id window) { - assertf(_thread == thread::current(), "Attempted to set window context on a different thread!"); + if (not _display) { + return; + } + assertf(_thread == thread::current(), "Attempted to set window context on a different thread!"); lock_guard guard(_lock); + _windows[window]->end_frame(); } } // fennec \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1110d8b..335d98e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,6 +9,8 @@ add_executable(fennec-test tests/lang/test_metaprogramming.h tests/lang/test_function.h tests/test_threading.h + tests/test_core.h + tests/core/test_event.h ) target_compile_definitions(fennec-test PUBLIC FENNEC_TEST_CWD="${CMAKE_SOURCE_DIR}/bin/${FENNEC_BUILD_NAME}" diff --git a/test/main.cpp b/test/main.cpp index fde4b65..0f35320 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -22,6 +22,7 @@ #include "test.h" #include "tests/test_containers.h" +#include "tests/test_core.h" #include "tests/test_string.h" #include "tests/test_format.h" #include "tests/test_filesystem.h" @@ -77,6 +78,11 @@ int main(int, char **) fennec::test::fennec_test_rtti(); fennec_test_spacer(3); + fennec_test_header("core library"); + fennec_test_spacer(2); + fennec::test::fennec_test_core(); + fennec_test_spacer(3); + fennec_test_header("platform library"); fennec_test_spacer(2); fennec::test::fennec_test_platform(); diff --git a/test/tests/core/test_event.h b/test/tests/core/test_event.h new file mode 100644 index 0000000..370d7bb --- /dev/null +++ b/test/tests/core/test_event.h @@ -0,0 +1,76 @@ +// ===================================================================================================================== +// fennec, a free and open source game engine +// Copyright © 2025 Medusa Slockbower +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ===================================================================================================================== + +/// +/// \file test_event.h +/// \brief +/// +/// +/// \details +/// \author Medusa Slockbower +/// +/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) +/// +/// + + +#ifndef FENNEC_TEST_CORE_EVENT_H +#define FENNEC_TEST_CORE_EVENT_H + +#include "../../test.h" +#include + +namespace fennec::test +{ + +struct test_event : public event { + FENNEC_RTTI_CLASS_ENABLE(event) { + } +}; + +struct test_listener : public event_listener { + bool handled = { false }; + + void handle_event(event* event) override { + handled = true; + fennec_test_run(event->get_type(), type::get()); + } + + FENNEC_RTTI_CLASS_ENABLE(event_listener) { + } +}; + +inline void fennec_test_core_event() { + test_listener listener; + event::add_listener(&listener); + + event::dispatch(); + event::handle_events(); + + fennec_test_run(listener.handled, true); + + listener.handled = false; + + event::dispatch_immediate(); + + fennec_test_run(listener.handled, true); +} + +} + +#endif // FENNEC_TEST_CORE_EVENT_H diff --git a/test/tests/test_core.h b/test/tests/test_core.h new file mode 100644 index 0000000..38685ed --- /dev/null +++ b/test/tests/test_core.h @@ -0,0 +1,52 @@ +// ===================================================================================================================== +// fennec, a free and open source game engine +// Copyright © 2025 Medusa Slockbower +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ===================================================================================================================== + +/// +/// \file test_core.h +/// \brief +/// +/// +/// \details +/// \author Medusa Slockbower +/// +/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) +/// +/// + + +#ifndef FENNEC_TEST_CORE_H +#define FENNEC_TEST_CORE_H + +#include "../test.h" +#include "core/test_event.h" + +namespace fennec::test +{ + +inline void fennec_test_core() { + fennec_test_subheader("events"); + fennec_test_spacer(2); + fennec_test_core_event(); + fennec_test_spacer(3); + + +} + +} + +#endif // FENNEC_TEST_CORE_H \ No newline at end of file