- testing for current threading structures

This commit is contained in:
2025-12-20 17:35:54 -05:00
parent 9f499c933d
commit 1f6637408d
14 changed files with 360 additions and 44 deletions

View File

@@ -248,6 +248,9 @@ fennec_add_sources(
# threading ============================================================================================================ # threading ============================================================================================================
include/fennec/threading/atomic.h 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/thread.h
include/fennec/threading/detail/_thread.h include/fennec/threading/detail/_thread.h
@@ -302,8 +305,6 @@ add_subdirectory(test)
add_library(fennec STATIC add_library(fennec STATIC
${FENNEC_SOURCES} ${FENNEC_SOURCES}
include/fennec/threading/mutex.h
include/fennec/threading/lock_guard.h
) )

View File

@@ -69,4 +69,15 @@
#ifndef FENNEC_CORE_ENGINE_H #ifndef FENNEC_CORE_ENGINE_H
#define FENNEC_CORE_ENGINE_H #define FENNEC_CORE_ENGINE_H
namespace fennec
{
class engine {
public:
private:
};
}
#endif // FENNEC_CORE_ENGINE_H #endif // FENNEC_CORE_ENGINE_H

View File

@@ -66,19 +66,15 @@ struct event {
/// \param listener the listener to remove /// \param listener the listener to remove
static void remove_listener(event_listener* listener); static void remove_listener(event_listener* listener);
/// template<typename EventT, typename...ArgsT>
/// \brief Dispatch an event static void dispatch_immediate(ArgsT&&...args) {
/// \tparam EventT The event type
/// \param event the event to dispatch
template<typename EventT>
static void dispatch(EventT* event) {
event::dispatch(event);
} }
/// template<typename EventT, typename...ArgsT>
/// \brief Dispatch an event static void dispatch_next_tick(ArgsT&&...args) {
/// \param event the event to dispatch
static void dispatch(event* event); }
#ifndef FENNEC_DOXYGEN #ifndef FENNEC_DOXYGEN
FENNEC_RTTI_CLASS_ENABLE() { FENNEC_RTTI_CLASS_ENABLE() {

View File

@@ -686,7 +686,7 @@ public:
/// ///
/// \brief Clear the block of memory, setting all bytes to 0. /// \brief Clear the block of memory, setting all bytes to 0.
constexpr void clear() noexcept { constexpr void clear() noexcept {
fennec::memset(_data, 0, _capacity * sizeof(T)); fennec::memset(static_cast<void*>(_data), 0, _capacity * sizeof(T));
} }
/// ///

View File

@@ -141,8 +141,7 @@ public:
/// \returns a reference to self /// \returns a reference to self
constexpr unique_ptr& operator=(unique_ptr&& r) noexcept { constexpr unique_ptr& operator=(unique_ptr&& r) noexcept {
_delete = r._delete; _delete = r._delete;
_handle = r._handle; fennec::swap(_handle, r._handle);
r._handle = nullptr;
return *this; return *this;
} }
@@ -206,6 +205,9 @@ public:
return _handle != nullptr; return _handle != nullptr;
} }
element_t& operator*() const {
return *_handle;
}
private: private:
delete_t _delete; delete_t _delete;

View File

@@ -36,6 +36,9 @@
#include <fennec/memory/pointers.h> #include <fennec/memory/pointers.h>
#include <fennec/containers/object_pool.h> #include <fennec/containers/object_pool.h>
#include <fennec/platform/interface/window.h>
#include <fennec/threading/lock_guard.h>
#include <fennec/threading/mutex.h>
#include <fennec/threading/thread.h> #include <fennec/threading/thread.h>
namespace fennec namespace fennec
@@ -45,14 +48,33 @@ namespace fennec
/// \brief class for handling display servers and windows /// \brief class for handling display servers and windows
class window_manager { class window_manager {
// Definitions ========================================================================================================= // 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: private:
using server_t = unique_ptr<display_server>; using server_t = unique_ptr<display_server>;
using window_t = unique_ptr<window>; using window_t = unique_ptr<window>;
using window_pool_t = object_pool<window_t>; using window_pool_t = object_pool<window_t>;
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 =========================================================================================== // Constructors & Destructor ===========================================================================================
public: public:
/// ///
/// \brief constructor /// \brief constructor
/// \param platform the platform /// \param platform the platform
@@ -71,16 +93,82 @@ public:
void shutdown(); //!< shutdown the window system void shutdown(); //!< shutdown the window system
void dispatch(); //!< dispatch the commands to the 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 =============================================================================================== // 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: private:
mutable mutex _lock;
thread::id _thread; thread::id _thread;
platform* _platform; platform* _platform;
server_t _display; server_t _display;
window_pool_t _windows; 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 } // fennec

View File

@@ -31,6 +31,8 @@
#ifndef FENNEC_RTTI_SINGLETON_H #ifndef FENNEC_RTTI_SINGLETON_H
#define FENNEC_RTTI_SINGLETON_H #define FENNEC_RTTI_SINGLETON_H
#include <fennec/lang/type_traits.h>
namespace fennec namespace fennec
{ {

View File

@@ -55,8 +55,8 @@ struct atomic {
// Assertions ========================================================================================================== // Assertions ==========================================================================================================
public: public:
static_assert(is_integral_v<T>, "fennec::atomic not defined for the provided type. Default implementation" static_assert(is_integral_v<T> or is_pointer_v<T>, "fennec::atomic not defined for the provided type. Default "
"only supports integral types."); "implementation only supports integral & pointer types.");
@@ -379,55 +379,55 @@ public:
// Bit Operations ====================================================================================================== // 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<T> {
return ::atomic_fetch_and_explicit(&_value, x, order); 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<T> {
return ::atomic_fetch_and_explicit(&_value, x, order); return ::atomic_fetch_and_explicit(&_value, x, order);
} }
T operator&=(const T x) noexcept { T operator&=(const T x) noexcept requires is_integral_v<T> {
return this->fetch_and(x) & x; return this->fetch_and(x) & x;
} }
T operator&=(const T x) volatile noexcept { T operator&=(const T x) volatile noexcept requires is_integral_v<T> {
return this->fetch_and(x) & x; 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<T> {
return ::atomic_fetch_or_explicit(&_value, x, order); 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<T> {
return ::atomic_fetch_or_explicit(&_value, x, order); return ::atomic_fetch_or_explicit(&_value, x, order);
} }
T operator|=(const T x) noexcept { T operator|=(const T x) noexcept requires is_integral_v<T> {
return this->fetch_or(x) & x; return this->fetch_or(x) & x;
} }
T operator|=(const T x) volatile noexcept { T operator|=(const T x) volatile noexcept requires is_integral_v<T> {
return this->fetch_or(x) & x; 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<T> {
return ::atomic_fetch_xor_explicit(&_value, x, order); 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<T> {
return ::atomic_fetch_xor_explicit(&_value, x, order); return ::atomic_fetch_xor_explicit(&_value, x, order);
} }
T operator^=(const T x) noexcept { T operator^=(const T x) noexcept requires is_integral_v<T> {
return this->fetch_xor(x) & x; return this->fetch_xor(x) & x;
} }
T operator^=(const T x) volatile noexcept { T operator^=(const T x) volatile noexcept requires is_integral_v<T> {
return this->fetch_xor(x) & x; return this->fetch_xor(x) & x;
} }

View File

@@ -96,12 +96,12 @@ constexpr T atomic_load_explicit(_Atomic T* x, memory_order memorder) {
// atomic_store ======================================================================================================== // atomic_store ========================================================================================================
template<typename T> template<typename T>
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); return ::__atomic_store_n(x, val, memory_order_seq_cst);
} }
template<typename T> template<typename T>
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); 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 ===================================================================================================== // atomic_exchange =====================================================================================================
template<typename T> template<typename T>
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); return ::__atomic_exchange_n(x, val, memory_order_seq_cst);
} }
template<typename T> template<typename T>
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); return ::__atomic_exchange_n(x, val, memorder);
} }

View File

@@ -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 <https://www.gnu.org/licenses/>.
// =====================================================================================================================
///
/// \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 <fennec/memory/allocator.h>
#include <fennec/memory/pointers.h>
#include <fennec/threading/atomic.h>
namespace fennec
{
///
/// \brief Multiple-Producer Single-Consumer Wait-free Queue
/// \tparam TypeT The held type
template<typename TypeT>
struct mpscq {
// Definitions =========================================================================================================
public:
using value_t = TypeT;
using elem_t = atomic<TypeT*>;
private:
using table_t = allocation<elem_t>;
using counter_t = atomic<size_t>;
// Constructors & Destructor ===========================================================================================
public:
mpscq() = delete;
mpscq(size_t cap)
: _data(cap)
, _head(0), _count(0)
, _tail(0) {
}
~mpscq() {
for (TypeT* it : _data) {
unique_ptr<TypeT> 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<TypeT>(x));
}
template<typename...ArgsT>
bool emplace(ArgsT&&...args) {
return this->_push(fennec::forward<ArgsT>(args)...);
}
// Consumers ===========================================================================================================
unique_ptr<TypeT> pop() {
unique_ptr<TypeT> 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<typename...ArgsT>
bool _push(ArgsT&&...args) {
unique_ptr<TypeT> ptr = fennec::make_unique<TypeT>(fennec::forward<ArgsT>(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

View File

@@ -148,6 +148,10 @@ public:
return pthread_self(); return pthread_self();
} }
static void yield() {
assertf(sched_yield() == 0, "Error yielding thread!");
}
id get_id() const { id get_id() const {
return _handle; return _handle;
} }

View File

@@ -19,6 +19,7 @@
#include <fennec/containers/dynarray.h> #include <fennec/containers/dynarray.h>
#include <fennec/containers/set.h> #include <fennec/containers/set.h>
#include <fennec/core/event.h> #include <fennec/core/event.h>
#include <fennec/threading/mpscq.h>
namespace fennec 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);
}
}
} }

View File

@@ -23,6 +23,7 @@
#include <fennec/platform/interface/platform.h> #include <fennec/platform/interface/platform.h>
#include <fennec/platform/interface/display_server.h> #include <fennec/platform/interface/display_server.h>
#include <fennec/platform/interface/window.h> #include <fennec/platform/interface/window.h>
#include <fennec/threading/lock_guard.h>
namespace fennec { namespace fennec {
@@ -85,7 +86,29 @@ void window_manager::shutdown() {
void window_manager::dispatch() { void window_manager::dispatch() {
assertf(_thread == thread::current(), "Attempted to dispatch Window Manager on a different thread!"); assertf(_thread == thread::current(), "Attempted to dispatch Window Manager on a different thread!");
lock_guard guard(_lock);
_display->dispatch(); _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 } // fennec

View File

@@ -32,8 +32,10 @@
#ifndef FENNEC_TEST_THREADING_H #ifndef FENNEC_TEST_THREADING_H
#define FENNEC_TEST_THREADING_H #define FENNEC_TEST_THREADING_H
#include <chrono>
#include <fennec/threading/atomic.h> #include <fennec/threading/atomic.h>
#include <fennec/threading/lock_guard.h> #include <fennec/threading/lock_guard.h>
#include <fennec/threading/mpscq.h>
#include <fennec/threading/mutex.h> #include <fennec/threading/mutex.h>
#include <fennec/threading/thread.h> #include <fennec/threading/thread.h>
@@ -81,15 +83,56 @@ inline void fennec_test_threading_run_test_mutex(array<thread, ThreadsV>& thread
} }
inline void fennec_test_threading_test_mpscq_producer(mpscq<size_t>* 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<size_t>* queue, atomic<size_t>* res, atomic<bool>* done) {
while (not done->load() or not queue->empty()) {
unique_ptr<size_t> ptr = queue->pop();
if (ptr) {
*res += *ptr;
}
}
}
template<size_t ThreadsV>
inline void fennec_test_threading_run_test_mpscq(array<thread, ThreadsV>& threads, size_t N = 1000) {
mpscq<size_t> queue(N * ThreadsV);
for (size_t i = 1; i < ThreadsV; ++i) {
threads[i] = thread(fennec_test_threading_test_mpscq_producer, &queue, N);
}
atomic<size_t> test;
atomic<bool> 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<typename ReturnT, typename...ParamsT, typename...ArgsT>
inline double fennec_test_threading_timed(ReturnT (func)(ParamsT...), ArgsT&&...args) {
auto start = std::chrono::high_resolution_clock::now();
func(fennec::forward<ArgsT>(args)...);
return std::chrono::duration<double>(std::chrono::high_resolution_clock::now() - start).count();
}
inline void fennec_test_threading() { inline void fennec_test_threading() {
static constexpr size_t N = 1000, threads = 4; static constexpr size_t N = 100000, threads = 8;
array<thread, threads> arr; array<thread, threads> arr;
fennec_test_threading_run_test_atomic(arr, N); std::cout << fennec_test_threading_timed(fennec_test_threading_run_test_atomic<threads>, arr, N) << "s\n";
fennec_test_threading_run_test_mutex(arr, N); std::cout << fennec_test_threading_timed(fennec_test_threading_run_test_mutex<threads>, arr, N) << "s\n";
std::cout << fennec_test_threading_timed(fennec_test_threading_run_test_mpscq<threads>, arr, N) << "s\n";
} }
} }