diff --git a/CMakeLists.txt b/CMakeLists.txt index a502b4e..9034c0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -248,6 +248,9 @@ fennec_add_sources( # threading ============================================================================================================ include/fennec/threading/atomic.h + include/fennec/threading/lock_guard.h + include/fennec/threading/mpscq.h + include/fennec/threading/mutex.h include/fennec/threading/thread.h include/fennec/threading/detail/_thread.h @@ -302,8 +305,6 @@ add_subdirectory(test) add_library(fennec STATIC ${FENNEC_SOURCES} - include/fennec/threading/mutex.h - include/fennec/threading/lock_guard.h ) diff --git a/include/fennec/core/engine.h b/include/fennec/core/engine.h index ac52d03..4acd709 100644 --- a/include/fennec/core/engine.h +++ b/include/fennec/core/engine.h @@ -69,4 +69,15 @@ #ifndef FENNEC_CORE_ENGINE_H #define FENNEC_CORE_ENGINE_H +namespace fennec +{ + +class engine { +public: + +private: +}; + +} + #endif // FENNEC_CORE_ENGINE_H diff --git a/include/fennec/core/event.h b/include/fennec/core/event.h index 1b5b910..e44a648 100644 --- a/include/fennec/core/event.h +++ b/include/fennec/core/event.h @@ -66,19 +66,15 @@ struct event { /// \param listener the listener to remove static void remove_listener(event_listener* listener); - /// - /// \brief Dispatch an event - /// \tparam EventT The event type - /// \param event the event to dispatch - template - static void dispatch(EventT* event) { - event::dispatch(event); + template + static void dispatch_immediate(ArgsT&&...args) { + } - /// - /// \brief Dispatch an event - /// \param event the event to dispatch - static void dispatch(event* event); + template + static void dispatch_next_tick(ArgsT&&...args) { + + } #ifndef FENNEC_DOXYGEN FENNEC_RTTI_CLASS_ENABLE() { diff --git a/include/fennec/memory/allocator.h b/include/fennec/memory/allocator.h index 1fd31cb..a2b454e 100644 --- a/include/fennec/memory/allocator.h +++ b/include/fennec/memory/allocator.h @@ -686,7 +686,7 @@ public: /// /// \brief Clear the block of memory, setting all bytes to 0. constexpr void clear() noexcept { - fennec::memset(_data, 0, _capacity * sizeof(T)); + fennec::memset(static_cast(_data), 0, _capacity * sizeof(T)); } /// diff --git a/include/fennec/memory/pointers.h b/include/fennec/memory/pointers.h index 1cd6b63..20137b9 100644 --- a/include/fennec/memory/pointers.h +++ b/include/fennec/memory/pointers.h @@ -141,8 +141,7 @@ public: /// \returns a reference to self constexpr unique_ptr& operator=(unique_ptr&& r) noexcept { _delete = r._delete; - _handle = r._handle; - r._handle = nullptr; + fennec::swap(_handle, r._handle); return *this; } @@ -206,6 +205,9 @@ public: return _handle != nullptr; } + element_t& operator*() const { + return *_handle; + } private: delete_t _delete; diff --git a/include/fennec/platform/window_manager.h b/include/fennec/platform/window_manager.h index f7706db..8d574e7 100644 --- a/include/fennec/platform/window_manager.h +++ b/include/fennec/platform/window_manager.h @@ -36,6 +36,9 @@ #include #include +#include +#include +#include #include namespace fennec @@ -45,14 +48,33 @@ namespace fennec /// \brief class for handling display servers and windows class window_manager { // Definitions ========================================================================================================= +public: + using window_id = size_t; //!< type representing an id for a window + + static constexpr window_id nullid = -1; //!< constant representing a null window + private: using server_t = unique_ptr; using window_t = unique_ptr; using window_pool_t = object_pool; + using config = window::config; + + enum command_ : uint8_t { + command_set_flag = 0, + command_resize + }; + + struct command { + uint8_t cmd; + window_id win; + void* data; + }; + // Constructors & Destructor =========================================================================================== public: + /// /// \brief constructor /// \param platform the platform @@ -71,16 +93,82 @@ public: void shutdown(); //!< shutdown the window system void dispatch(); //!< dispatch the commands to the system + window_id create_window(const config& config, window_id parent = nullid); + void begin(window_id window); + void end(window_id window); + // Thread-Safe Functions =============================================================================================== + window_id get_parent(window_id window) const { + lock_guard guard(_lock); + return _parent(window); + } + window_id get_root(window_id window) const { + lock_guard guard(_lock); + return _root(window); + } + + ivec2 get_size(window_id window) const { + lock_guard guard(_lock); + return _size(window); + } + ivec2 get_position(window_id window) const { + lock_guard guard(_lock); + return _position(window); + } + + bool is_visible(window_id window) const { lock_guard guard(_lock); return _check_state(window, window::state_visible); } + bool is_child(window_id window) const { lock_guard guard(_lock); return _check_state(window, window::state_child); } + bool is_running(window_id window) const { lock_guard guard(_lock); return _check_state(window, window::state_running); } + bool is_suspended(window_id window) const { lock_guard guard(_lock); return _check_state(window, window::state_suspended); } + + bool get_flag(window_id window, uint8_t flag) { + lock_guard guard(_lock); + return _get_flag(window, flag); + } + + bool is_always_on_top(window_id window) { return get_flag(window, window::flag_always_on_top); } + bool is_borderless(window_id window) { return get_flag(window, window::flag_borderless); } + bool is_modal(window_id window) { return get_flag(window, window::flag_modal); } + bool is_passing_mouse(window_id window) { return get_flag(window, window::flag_pass_mouse); } + bool is_popup(window_id window) { return get_flag(window, window::flag_popup); } + bool is_resizable(window_id window) { return get_flag(window, window::flag_resizable); } + bool is_transparent(window_id window) { return get_flag(window, window::flag_transparent); } + bool is_no_focus(window_id window) { return get_flag(window, window::flag_no_focus); } + + + bool set_flag(window_id window, uint8_t flag, bool val); + + bool set_always_on_top(window_id window, bool val) { return set_flag(window, window::flag_always_on_top, val); } + bool set_borderless(window_id window, bool val) { return set_flag(window, window::flag_borderless, val); } + bool set_modal(window_id window, bool val) { return set_flag(window, window::flag_modal, val); } + bool set_passing_mouse(window_id window, bool val) { return set_flag(window, window::flag_pass_mouse, val); } + bool set_popup(window_id window, bool val) { return set_flag(window, window::flag_popup, val); } + bool set_resizable(window_id window, bool val) { return set_flag(window, window::flag_resizable, val); } + bool set_transparent(window_id window, bool val) { return set_flag(window, window::flag_transparent, val); } + bool set_no_focus(window_id window, bool val) { return set_flag(window, window::flag_no_focus, val); } private: + mutable mutex _lock; thread::id _thread; platform* _platform; server_t _display; window_pool_t _windows; + + + window_id _parent(window_id id) const; + window_id _root(window_id id) const; + + ivec2 _size(window_id id) const; + ivec2 _position(window_id id) const; + + bool _check_state(window_id id, uint8_t state) const; + + bool _get_flag(window_id id, uint8_t flag) const; + bool _set_flag(window_id id, uint8_t flag, bool val); + }; } // fennec diff --git a/include/fennec/rtti/singleton.h b/include/fennec/rtti/singleton.h index 5ebc392..776761b 100644 --- a/include/fennec/rtti/singleton.h +++ b/include/fennec/rtti/singleton.h @@ -31,6 +31,8 @@ #ifndef FENNEC_RTTI_SINGLETON_H #define FENNEC_RTTI_SINGLETON_H +#include + namespace fennec { diff --git a/include/fennec/threading/atomic.h b/include/fennec/threading/atomic.h index 16294d1..11180ce 100644 --- a/include/fennec/threading/atomic.h +++ b/include/fennec/threading/atomic.h @@ -55,8 +55,8 @@ struct atomic { // Assertions ========================================================================================================== public: - static_assert(is_integral_v, "fennec::atomic not defined for the provided type. Default implementation" - "only supports integral types."); + static_assert(is_integral_v or is_pointer_v, "fennec::atomic not defined for the provided type. Default " + "implementation only supports integral & pointer types."); @@ -379,55 +379,55 @@ public: // Bit Operations ====================================================================================================== - T fetch_and(const T x, memory_order order = memory_order_seq_cst) noexcept { + T fetch_and(const T x, memory_order order = memory_order_seq_cst) noexcept requires is_integral_v { return ::atomic_fetch_and_explicit(&_value, x, order); } - T fetch_and(const T x, memory_order order = memory_order_seq_cst) volatile noexcept { + T fetch_and(const T x, memory_order order = memory_order_seq_cst) volatile noexcept requires is_integral_v { return ::atomic_fetch_and_explicit(&_value, x, order); } - T operator&=(const T x) noexcept { + T operator&=(const T x) noexcept requires is_integral_v { return this->fetch_and(x) & x; } - T operator&=(const T x) volatile noexcept { + T operator&=(const T x) volatile noexcept requires is_integral_v { return this->fetch_and(x) & x; } - T fetch_or(const T x, memory_order order = memory_order_seq_cst) noexcept { + T fetch_or(const T x, memory_order order = memory_order_seq_cst) noexcept requires is_integral_v { return ::atomic_fetch_or_explicit(&_value, x, order); } - T fetch_or(const T x, memory_order order = memory_order_seq_cst) volatile noexcept { + T fetch_or(const T x, memory_order order = memory_order_seq_cst) volatile noexcept requires is_integral_v { return ::atomic_fetch_or_explicit(&_value, x, order); } - T operator|=(const T x) noexcept { + T operator|=(const T x) noexcept requires is_integral_v { return this->fetch_or(x) & x; } - T operator|=(const T x) volatile noexcept { + T operator|=(const T x) volatile noexcept requires is_integral_v { return this->fetch_or(x) & x; } - T fetch_xor(const T x, memory_order order = memory_order_seq_cst) noexcept { + T fetch_xor(const T x, memory_order order = memory_order_seq_cst) noexcept requires is_integral_v { return ::atomic_fetch_xor_explicit(&_value, x, order); } - T fetch_xor(const T x, memory_order order = memory_order_seq_cst) volatile noexcept { + T fetch_xor(const T x, memory_order order = memory_order_seq_cst) volatile noexcept requires is_integral_v { return ::atomic_fetch_xor_explicit(&_value, x, order); } - T operator^=(const T x) noexcept { + T operator^=(const T x) noexcept requires is_integral_v { return this->fetch_xor(x) & x; } - T operator^=(const T x) volatile noexcept { + T operator^=(const T x) volatile noexcept requires is_integral_v { return this->fetch_xor(x) & x; } diff --git a/include/fennec/threading/detail/_atomic.h b/include/fennec/threading/detail/_atomic.h index f782b02..0ef86f2 100644 --- a/include/fennec/threading/detail/_atomic.h +++ b/include/fennec/threading/detail/_atomic.h @@ -96,12 +96,12 @@ constexpr T atomic_load_explicit(_Atomic T* x, memory_order memorder) { // atomic_store ======================================================================================================== template -constexpr bool atomic_store(_Atomic T* x, const T val) { +constexpr void atomic_store(_Atomic T* x, const T val) { return ::__atomic_store_n(x, val, memory_order_seq_cst); } template -constexpr bool atomic_store_explicit(_Atomic T* x, const T val, memory_order memorder) { +constexpr void atomic_store_explicit(_Atomic T* x, const T val, memory_order memorder) { return ::__atomic_store_n(x, val, memorder); } @@ -110,12 +110,12 @@ constexpr bool atomic_store_explicit(_Atomic T* x, const T val, memory_order mem // atomic_exchange ===================================================================================================== template -constexpr bool atomic_exchange(_Atomic T* x, const T val) { +constexpr T atomic_exchange(_Atomic T* x, const T val) { return ::__atomic_exchange_n(x, val, memory_order_seq_cst); } template -constexpr bool atomic_exchange_explicit(_Atomic T* x, const T val, memory_order memorder) { +constexpr T atomic_exchange_explicit(_Atomic T* x, const T val, memory_order memorder) { return ::__atomic_exchange_n(x, val, memorder); } diff --git a/include/fennec/threading/mpscq.h b/include/fennec/threading/mpscq.h new file mode 100644 index 0000000..765cb35 --- /dev/null +++ b/include/fennec/threading/mpscq.h @@ -0,0 +1,151 @@ +// ===================================================================================================================== +// 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 mpscq.h +/// \brief +/// +/// +/// \details +/// \author Medusa Slockbower +/// +/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) +/// +/// + + +// Adapted to C++ from https://github.com/dbittman/waitfree-mpsc-queue + + +#ifndef FENNEC_THREADING_MPSCQ_H +#define FENNEC_THREADING_MPSCQ_H + +#include +#include +#include + +namespace fennec +{ + +/// +/// \brief Multiple-Producer Single-Consumer Wait-free Queue +/// \tparam TypeT The held type +template +struct mpscq { +// Definitions ========================================================================================================= +public: + using value_t = TypeT; + using elem_t = atomic; + +private: + using table_t = allocation; + using counter_t = atomic; + + +// Constructors & Destructor =========================================================================================== +public: + mpscq() = delete; + + mpscq(size_t cap) + : _data(cap) + , _head(0), _count(0) + , _tail(0) { + } + + ~mpscq() { + for (TypeT* it : _data) { + unique_ptr ptr(it); + } + _data.deallocate(); + } + + mpscq(const mpscq&) = delete; + + +// Properties ========================================================================================================== + + size_t size() const { + return _count.load(memory_order_relaxed); + } + + size_t capacity() const { + return _data.capacity(); + } + + bool empty() const { + return size() == 0; + } + + +// Producers =========================================================================================================== + + bool push(const TypeT& x) { + return this->_push(x); + } + + bool push(TypeT&& x) noexcept { + return this->_push(fennec::forward(x)); + } + + template + bool emplace(ArgsT&&...args) { + return this->_push(fennec::forward(args)...); + } + + +// Consumers =========================================================================================================== + + unique_ptr pop() { + unique_ptr res(_data[_tail].exchange(nullptr, memory_order_acquire)); + if (not res) { + return nullptr; + } + _tail = (_tail + 1) % _data.capacity(); + size_t r = _count.fetch_sub(1, memory_order_release); + assertf(r > 0, "Popped element from empty queue!"); + return fennec::move(res); + } + +private: + table_t _data; + counter_t _head, _count; + size_t _tail; + + template + bool _push(ArgsT&&...args) { + unique_ptr ptr = fennec::make_unique(fennec::forward(args)...); + + size_t cap = _data.capacity(); + size_t ct = _count.fetch_add(1, memory_order_acquire); + if (ct >= cap) { + _count.fetch_sub(1, memory_order_release); + return false; + } + + size_t hd = _head.fetch_add(1, memory_order_acquire); + assertf(_data[hd % cap] == nullptr, "Encountered occupied slot."); + TypeT* res = _data[hd % cap].exchange(ptr.get(), memory_order_release); + assertf(res == nullptr, "Encountered occupied slot."); + ptr.release(); + return true; + } +}; + +} + +#endif // FENNEC_THREADING_MPSCQ_H \ No newline at end of file diff --git a/include/fennec/threading/thread.h b/include/fennec/threading/thread.h index 10cd996..ee1208e 100644 --- a/include/fennec/threading/thread.h +++ b/include/fennec/threading/thread.h @@ -148,6 +148,10 @@ public: return pthread_self(); } + static void yield() { + assertf(sched_yield() == 0, "Error yielding thread!"); + } + id get_id() const { return _handle; } diff --git a/source/core/event.cpp b/source/core/event.cpp index 441c2a2..f3f63e4 100644 --- a/source/core/event.cpp +++ b/source/core/event.cpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace fennec { @@ -38,10 +39,4 @@ void event::remove_listener(event_listener* listener) { } } -void event::dispatch(event* event) { - for (auto& it : listeners[event->get_type().id()]) { - it->handle_event(event); - } -} - } diff --git a/source/platform/window_manager.cpp b/source/platform/window_manager.cpp index c13804b..8af9e14 100644 --- a/source/platform/window_manager.cpp +++ b/source/platform/window_manager.cpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace fennec { @@ -85,7 +86,29 @@ void window_manager::shutdown() { void window_manager::dispatch() { 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!"); + + 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!"); + + 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!"); + + lock_guard guard(_lock); + _windows[window]->end_frame(); +} } // fennec \ No newline at end of file diff --git a/test/tests/test_threading.h b/test/tests/test_threading.h index a153c1e..caedcd1 100644 --- a/test/tests/test_threading.h +++ b/test/tests/test_threading.h @@ -32,8 +32,10 @@ #ifndef FENNEC_TEST_THREADING_H #define FENNEC_TEST_THREADING_H +#include #include #include +#include #include #include @@ -81,15 +83,56 @@ inline void fennec_test_threading_run_test_mutex(array& thread } + +inline void fennec_test_threading_test_mpscq_producer(mpscq* queue, size_t N = 1000) { + for (size_t i = 0; i < N; ++i) { + queue->emplace(1); + } +} + +inline void fennec_test_threading_test_mpscq_consumer(mpscq* queue, atomic* res, atomic* done) { + while (not done->load() or not queue->empty()) { + unique_ptr ptr = queue->pop(); + if (ptr) { + *res += *ptr; + } + } +} + +template +inline void fennec_test_threading_run_test_mpscq(array& threads, size_t N = 1000) { + mpscq queue(N * ThreadsV); + for (size_t i = 1; i < ThreadsV; ++i) { + threads[i] = thread(fennec_test_threading_test_mpscq_producer, &queue, N); + } + atomic test; + atomic done; + threads[0] = thread(fennec_test_threading_test_mpscq_consumer, &queue, &test, &done); + for (size_t i = 1; i < ThreadsV; ++i) { + threads[i].join(); + } + done.store(true); + threads[0].join(); + fennec_test_run(test.load(), N * (ThreadsV - 1)); +} + +template +inline double fennec_test_threading_timed(ReturnT (func)(ParamsT...), ArgsT&&...args) { + auto start = std::chrono::high_resolution_clock::now(); + func(fennec::forward(args)...); + return std::chrono::duration(std::chrono::high_resolution_clock::now() - start).count(); +} + + inline void fennec_test_threading() { - static constexpr size_t N = 1000, threads = 4; + static constexpr size_t N = 100000, threads = 8; array arr; - fennec_test_threading_run_test_atomic(arr, N); - fennec_test_threading_run_test_mutex(arr, N); - + std::cout << fennec_test_threading_timed(fennec_test_threading_run_test_atomic, arr, N) << "s\n"; + std::cout << fennec_test_threading_timed(fennec_test_threading_run_test_mutex, arr, N) << "s\n"; + std::cout << fennec_test_threading_timed(fennec_test_threading_run_test_mpscq, arr, N) << "s\n"; } }