From 9f499c933dd5cb2fb4851b3341e9d4707d2788e2 Mon Sep 17 00:00:00 2001 From: Medusa Slockbower Date: Fri, 19 Dec 2025 20:58:19 -0500 Subject: [PATCH] - more threading things TODO: documentation --- CMakeLists.txt | 2 + include/fennec/lang/detail/_type_transforms.h | 43 ++++++---- include/fennec/lang/function.h | 6 +- include/fennec/lang/utility.h | 9 +++ .../fennec/renderers/interface/gfxcontext.h | 12 --- include/fennec/renderers/opengl/lib/texture.h | 10 ++- include/fennec/rtti/type_data.h | 10 +-- include/fennec/threading/detail/_thread.h | 10 +-- include/fennec/threading/lock_guard.h | 58 ++++++++++++++ include/fennec/threading/mutex.h | 79 +++++++++++++++++++ include/fennec/threading/thread.h | 27 ++++--- test/tests/test_threading.h | 51 +++++++++++- 12 files changed, 264 insertions(+), 53 deletions(-) create mode 100644 include/fennec/threading/lock_guard.h create mode 100644 include/fennec/threading/mutex.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ae5cb68..a502b4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -302,6 +302,8 @@ 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/lang/detail/_type_transforms.h b/include/fennec/lang/detail/_type_transforms.h index a9a334f..6e7c776 100644 --- a/include/fennec/lang/detail/_type_transforms.h +++ b/include/fennec/lang/detail/_type_transforms.h @@ -25,6 +25,7 @@ namespace fennec::detail { template struct _add_pointer : type_identity {}; + template struct _add_pointer : type_identity {}; template struct _remove_pointer : type_identity {}; template struct _remove_pointer : type_identity {}; @@ -43,36 +44,48 @@ namespace fennec::detail template struct _remove_cv : type_identity {}; template - struct _decay : conditional<_is_const{}, _remove_cv, _add_pointer> {}; + struct _decay_selector : conditional_t<_is_const{}, _remove_cv, _add_pointer> {}; - template requires requires { typename TypeT::element_t; } - struct _decay : type_identity {}; + template requires requires { typename TypeT::decay_t; } + struct _decay_selector : type_identity {}; template - struct _decay : type_identity {}; + struct _decay_selector : type_identity {}; template - struct _decay : type_identity {}; + struct _decay_selector : type_identity {}; - template + template struct _decay { + using type = typename _decay_selector::type; + }; + + template struct _decay { + using type = typename _decay_selector::type; + }; + + template struct _decay { + using type = typename _decay_selector::type; + }; + + template struct _add_lvalue_reference { - using type = _Tp; + using type = TypeT; }; - template - struct _add_lvalue_reference<_Tp, void_t<_Tp&>> { - using type = _Tp&; + template + struct _add_lvalue_reference> { + using type = TypeT&; }; - template + template struct _add_rvalue_reference { - using type = _Tp; + using type = TypeT; }; - template - struct _add_rvalue_reference<_Tp, void_t<_Tp&&>> { - using type = _Tp&&; + template + struct _add_rvalue_reference> { + using type = TypeT&&; }; } diff --git a/include/fennec/lang/function.h b/include/fennec/lang/function.h index b2447b9..9a44a6e 100644 --- a/include/fennec/lang/function.h +++ b/include/fennec/lang/function.h @@ -53,7 +53,9 @@ class function { public: /// /// \brief default constructor - constexpr function() noexcept = default; + constexpr function() noexcept + : call(nullptr) { + } /// /// \brief destructor @@ -62,7 +64,7 @@ public: /// /// \brief copy constructor /// \param func the function to copy - constexpr function(const function& func) noexcept = default; + constexpr function(const function& func) noexcept = default; /// /// \brief move constructor diff --git a/include/fennec/lang/utility.h b/include/fennec/lang/utility.h index 4d013c4..ac98373 100644 --- a/include/fennec/lang/utility.h +++ b/include/fennec/lang/utility.h @@ -84,6 +84,15 @@ template constexpr T&& forward(remove_reference_t&& x) noexcept { #endif +/// \brief Copies \f$v\f$ to a new object of `decay_t` +/// \tparam T The type +/// \param v The object +/// \returns A stack allocated copy of \f$v\f$ in `decay_t` +template constexpr decay_t decay_copy(T&& v) { + return fennec::forward(v); +} + + /// /// \brief produces an x-value type to indicate \p x may be "moved" /// diff --git a/include/fennec/renderers/interface/gfxcontext.h b/include/fennec/renderers/interface/gfxcontext.h index 7e3631d..c18f21f 100644 --- a/include/fennec/renderers/interface/gfxcontext.h +++ b/include/fennec/renderers/interface/gfxcontext.h @@ -47,18 +47,6 @@ namespace fennec class gfxcontext { public: - enum texture_ : uint8_t { - texture_1d = 0, - texture_1d_array, - texture_2d, - texture_2d_array, - texture_multisample, - texture_multisample_array, - texture_cubemap, - texture_cubemap_array, - texture_3d, - }; - using handle_t = uint32_t; gfxresourcepool resources; diff --git a/include/fennec/renderers/opengl/lib/texture.h b/include/fennec/renderers/opengl/lib/texture.h index a69c059..eb8159b 100644 --- a/include/fennec/renderers/opengl/lib/texture.h +++ b/include/fennec/renderers/opengl/lib/texture.h @@ -235,7 +235,7 @@ public: /// \param layout The layout of the components in the pixel /// \param bytes The size of the image data in bytes, for compressed pixel formats texture(GLsizei size, GLsizei mips, - const void* faces[6], GLenum component = BYTE, GLenum layout = R, GLsizei = 0) requires is_2d and cubemap + const void* faces[6], GLenum component = BYTE, GLenum layout = R, GLsizei bytes = 0) requires is_2d and cubemap : _handle(NULL) , _width(size), _height(size), _depth(1) , _samples(1), _mips(mips) { @@ -247,6 +247,9 @@ public: glTexSubImage2D(base_cubemap_face + i, 0, 0, 0, _width, _height, layout, component, faces[i]); } } else if constexpr(compressed) { + for (int i = 0; i < cubemap_faces; ++i) { + glCompressedTexImage2D(base_cubemap_face + i, 0, format, _width, _height, 0, bytes, faces[i]); + } } else { for (int i = 0; i < cubemap_faces; ++i) { glTexImage2D(base_cubemap_face + i, 0, format, _width, _height, 0, layout, component, faces[i]); @@ -266,7 +269,7 @@ public: /// /// \details Requires OES_texture_cube_map_array texture(GLsizei size, GLsizei depth, GLsizei mips, - const void* data, GLenum component = BYTE, GLenum layout = R, GLsizei = 0) requires is_2d and cubemap + const void* data, GLenum component = BYTE, GLenum layout = R, GLsizei bytes = 0) requires is_2d and cubemap : _handle(NULL) , _width(size), _height(size), _depth(depth) , _samples(1), _mips(mips) { @@ -276,6 +279,9 @@ public: glTexStorage3D(type, _mips, format, _width, _height, _depth * 6); glTexSubImage3D(type, 0, 0, 0, 0, _width, _height, _depth * 6, layout, component, data); } else if constexpr(compressed) { + for (int i = 0; i < cubemap_faces; ++i) { + glCompressedTexImage3D(type, 0, format, _width, _height, _depth * 6, 0, bytes, data); + } } else { glTexImage3D(type, 0, format, _width, _height, _depth * 6, 0, layout, component, data); } diff --git a/include/fennec/rtti/type_data.h b/include/fennec/rtti/type_data.h index 10a532b..820fe3c 100644 --- a/include/fennec/rtti/type_data.h +++ b/include/fennec/rtti/type_data.h @@ -102,6 +102,11 @@ private: } public: + template + static type_data* get_data() { + return nullptr; + } + template static type_data* get_data() requires(not is_void_v) { auto& typelist = _typelist(); @@ -118,11 +123,6 @@ public: return typelist[uuid].get(); } - template - static type_data* get_data() { - return nullptr; - } - template static dynarray get_data(typelist) { return { diff --git a/include/fennec/threading/detail/_thread.h b/include/fennec/threading/detail/_thread.h index 59b7c88..a83cb66 100644 --- a/include/fennec/threading/detail/_thread.h +++ b/include/fennec/threading/detail/_thread.h @@ -42,15 +42,15 @@ namespace fennec::detail template using thread_proxy_t = tuple; template -void _execute_thread(thread_proxy_t, ArgsT...>& data, index_metasequence) { - auto func = data.template get<0>(); - func(fennec::move(data.template get()) ...); +void _execute_thread(thread_proxy_t& data, index_metasequence) { + auto func = fennec::move(fennec::get<0>(data)); + func(fennec::move(fennec::get(data)) ...); } template void* _run_thread(void* data) { - unique_ptr ptr(data); - detail::_execute_thread(*ptr, make_index_metasequence_t{}); + unique_ptr ptr(static_cast(data)); + detail::_execute_thread(*ptr.get(), make_index_metasequence_t{}); return nullptr; } diff --git a/include/fennec/threading/lock_guard.h b/include/fennec/threading/lock_guard.h new file mode 100644 index 0000000..10985b1 --- /dev/null +++ b/include/fennec/threading/lock_guard.h @@ -0,0 +1,58 @@ +// ===================================================================================================================== +// 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 lock_guard.h +/// \brief +/// +/// +/// \details +/// \author Medusa Slockbower +/// +/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) +/// +/// + + +#ifndef FENNEC_THREADING_LOCK_GUARD_H +#define FENNEC_THREADING_LOCK_GUARD_H + +namespace fennec +{ + +template +class lock_guard { +public: + lock_guard() = delete; + + explicit lock_guard(MutexT& mutex) + : mutex(mutex) { + mutex.lock(); + } + + ~lock_guard() { + mutex.unlock(); + } + +private: + MutexT& mutex; +}; + +} + +#endif // FENNEC_THREADING_LOCK_GUARD_H \ No newline at end of file diff --git a/include/fennec/threading/mutex.h b/include/fennec/threading/mutex.h new file mode 100644 index 0000000..7ef0f6f --- /dev/null +++ b/include/fennec/threading/mutex.h @@ -0,0 +1,79 @@ +// ===================================================================================================================== +// 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 mutex.h +/// \brief +/// +/// +/// \details +/// \author Medusa Slockbower +/// +/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) +/// +/// + + +#ifndef FENNEC_THREADING_MUTEX_H +#define FENNEC_THREADING_MUTEX_H + +#include +#include + +#include + +namespace fennec +{ + +class mutex { +public: + using handle_t = pthread_mutex_t; + + mutex() + : _handle() { + assertf((pthread_mutex_init(&_handle, nullptr) == 0), "Failed to initialize mutex.") + } + ~mutex() { + assertf((pthread_mutex_destroy(&_handle) == 0), "Failed to destroy mutex."); + } + + mutex(const mutex&) = delete; + mutex& operator=(const mutex&) = delete; + + void lock() { + assertf(pthread_mutex_lock(&_handle) == 0, "Error on lock()!"); + } + + bool try_lock() { + int res = pthread_mutex_trylock(&_handle); + assertf(res == EBUSY or res == 0, "Error on trylock()!"); + return res == 0; + } + + void unlock() { + assertf(pthread_mutex_unlock(&_handle) == 0, "Error on unlock()!"); + } + +private: + pthread_mutex_t _handle; + pthread_mutexattr_t _attr; +}; + +} + +#endif // FENNEC_THREADING_MUTEX_H \ No newline at end of file diff --git a/include/fennec/threading/thread.h b/include/fennec/threading/thread.h index 1294313..10cd996 100644 --- a/include/fennec/threading/thread.h +++ b/include/fennec/threading/thread.h @@ -33,12 +33,13 @@ #define FENNEC_THREADING_THREAD_H #include -#include -#include -#include + #include + #include +#include + namespace fennec { @@ -47,7 +48,7 @@ private: static constexpr pthread_t nullthread = 0; public: - using native_handle_type = pthread_t; + using handle_t = pthread_t; class id { public: @@ -89,10 +90,13 @@ public: requires(not is_same_v>) thread(FuncT&& func, ArgsT&&...args) : _handle(nullthread) { - using proxy_t = detail::thread_proxy_t, ArgsT...>; - unique_ptr proxy = fennec::make_unique(fennec::forward(func), fennec::forward(args)...); + using proxy_t = detail::thread_proxy_t, decay_t...>; + unique_ptr proxy = fennec::make_unique( + fennec::decay_copy(fennec::forward(func)), + fennec::decay_copy(fennec::forward(args))... + ); - int_t res = ::pthread_create(&_handle, nullptr, &detail::_run_thread(), proxy.get()); + int_t res = ::pthread_create(&_handle, nullptr, &detail::_run_thread, proxy.get()); assertf(res == 0, "Thread creation failed!"); proxy.release(); @@ -108,7 +112,8 @@ public: } ~thread() { - assertf(_handle == 0, "Attempted to destruct a running thread!"); + assertf(_handle == 0 or joinable(), "Attempted to destruct a running thread!"); + join(); } bool joinable() const noexcept { @@ -131,7 +136,7 @@ public: } int_t res = pthread_detach(_handle); - assertf(res == 0, "Failed to detatch thread!"); + assertf(res == 0, "Failed to detach thread!"); _handle = nullthread; } @@ -155,14 +160,14 @@ private: template <> struct hash { size_t operator()(thread::id v) const noexcept { - return hash()(v._id); + return hash()(v._id); } }; template<> struct formatter { string operator()(const format_arg& fmt, thread::id v) const noexcept { - return formatter()(fmt, v._id); + return formatter()(fmt, v._id); } }; diff --git a/test/tests/test_threading.h b/test/tests/test_threading.h index 3e641e4..a153c1e 100644 --- a/test/tests/test_threading.h +++ b/test/tests/test_threading.h @@ -33,13 +33,62 @@ #define FENNEC_TEST_THREADING_H #include +#include +#include +#include namespace fennec::test { +inline void fennec_test_threading_test_atomic(atomic* test_atomic, size_t N = 1000) { + for (size_t i = 0; i < N; ++i) { + ++*test_atomic; + } +} + +template +inline void fennec_test_threading_run_test_atomic(array& threads, size_t N = 1000) { + atomic test = 0; + for (thread& t : threads) { + t = thread(fennec_test_threading_test_atomic, &test, N); + } + for (thread& t : threads) { + t.join(); + } + fennec_test_run(test.load(), N * ThreadsV); +} + + + +inline void fennec_test_threading_test_mutex(size_t* var, mutex* m, size_t N = 1000) { + for (size_t i = 0; i < N; ++i) { + lock_guard guard(*m); + ++*var; + } +} + +template +inline void fennec_test_threading_run_test_mutex(array& threads, size_t N = 1000) { + mutex m; + size_t test = 0; + for (thread& t : threads) { + t = thread(fennec_test_threading_test_mutex, &test, &m, N); + } + for (thread& t : threads) { + t.join(); + } + fennec_test_run(test, N * ThreadsV); +} + + inline void fennec_test_threading() { - atomic test; + static constexpr size_t N = 1000, threads = 4; + + array arr; + + fennec_test_threading_run_test_atomic(arr, N); + fennec_test_threading_run_test_mutex(arr, N); }