- more threading things

TODO: documentation
This commit is contained in:
2025-12-19 20:58:19 -05:00
parent 88e33bdcc8
commit 9f499c933d
12 changed files with 264 additions and 53 deletions

View File

@@ -302,6 +302,8 @@ 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

@@ -25,6 +25,7 @@
namespace fennec::detail namespace fennec::detail
{ {
template<typename TypeT> struct _add_pointer : type_identity<TypeT*> {}; template<typename TypeT> struct _add_pointer : type_identity<TypeT*> {};
template<typename TypeT> struct _add_pointer<TypeT&> : type_identity<TypeT*> {};
template<typename TypeT> struct _remove_pointer : type_identity<TypeT> {}; template<typename TypeT> struct _remove_pointer : type_identity<TypeT> {};
template<typename TypeT> struct _remove_pointer<TypeT*> : type_identity<TypeT> {}; template<typename TypeT> struct _remove_pointer<TypeT*> : type_identity<TypeT> {};
@@ -43,36 +44,48 @@ namespace fennec::detail
template<typename TypeT> struct _remove_cv<const volatile TypeT> : type_identity<TypeT> {}; template<typename TypeT> struct _remove_cv<const volatile TypeT> : type_identity<TypeT> {};
template<typename TypeT> template<typename TypeT>
struct _decay : conditional<_is_const<const TypeT>{}, _remove_cv<TypeT>, _add_pointer<TypeT>> {}; struct _decay_selector : conditional_t<_is_const<const TypeT>{}, _remove_cv<TypeT>, _add_pointer<TypeT>> {};
template<typename TypeT> requires requires { typename TypeT::element_t; } template<typename TypeT> requires requires { typename TypeT::decay_t; }
struct _decay<TypeT> : type_identity<typename TypeT::decay_t> {}; struct _decay_selector<TypeT> : type_identity<typename TypeT::decay_t> {};
template<typename TypeT, size_t N> template<typename TypeT, size_t N>
struct _decay<TypeT[N]> : type_identity<TypeT*> {}; struct _decay_selector<TypeT[N]> : type_identity<TypeT*> {};
template<typename TypeT> template<typename TypeT>
struct _decay<TypeT[]> : type_identity<TypeT*> {}; struct _decay_selector<TypeT[]> : type_identity<TypeT*> {};
template<typename _Tp, typename = void> template<typename TypeT> struct _decay {
using type = typename _decay_selector<TypeT>::type;
};
template<typename TypeT> struct _decay<TypeT&> {
using type = typename _decay_selector<TypeT>::type;
};
template<typename TypeT> struct _decay<TypeT&&> {
using type = typename _decay_selector<TypeT>::type;
};
template<typename TypeT, typename = void>
struct _add_lvalue_reference { struct _add_lvalue_reference {
using type = _Tp; using type = TypeT;
}; };
template<typename _Tp> template<typename TypeT>
struct _add_lvalue_reference<_Tp, void_t<_Tp&>> { struct _add_lvalue_reference<TypeT, void_t<TypeT&>> {
using type = _Tp&; using type = TypeT&;
}; };
template<typename _Tp, typename = void> template<typename TypeT, typename = void>
struct _add_rvalue_reference { struct _add_rvalue_reference {
using type = _Tp; using type = TypeT;
}; };
template<typename _Tp> template<typename TypeT>
struct _add_rvalue_reference<_Tp, void_t<_Tp&&>> { struct _add_rvalue_reference<TypeT, void_t<TypeT&&>> {
using type = _Tp&&; using type = TypeT&&;
}; };
} }

View File

@@ -53,7 +53,9 @@ class function<ReturnT(ArgsT...)> {
public: public:
/// ///
/// \brief default constructor /// \brief default constructor
constexpr function() noexcept = default; constexpr function() noexcept
: call(nullptr) {
}
/// ///
/// \brief destructor /// \brief destructor

View File

@@ -84,6 +84,15 @@ template<typename T> constexpr T&& forward(remove_reference_t<T>&& x) noexcept {
#endif #endif
/// \brief Copies \f$v\f$ to a new object of `decay_t<T>`
/// \tparam T The type
/// \param v The object
/// \returns A stack allocated copy of \f$v\f$ in `decay_t<T>`
template<typename T> constexpr decay_t<T> decay_copy(T&& v) {
return fennec::forward<T>(v);
}
/// ///
/// \brief produces an x-value type to indicate \p x may be "moved" /// \brief produces an x-value type to indicate \p x may be "moved"
/// ///

View File

@@ -47,18 +47,6 @@ namespace fennec
class gfxcontext { class gfxcontext {
public: 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; using handle_t = uint32_t;
gfxresourcepool resources; gfxresourcepool resources;

View File

@@ -235,7 +235,7 @@ public:
/// \param layout The layout of the components in the pixel /// \param layout The layout of the components in the pixel
/// \param bytes The size of the image data in bytes, for compressed pixel formats /// \param bytes The size of the image data in bytes, for compressed pixel formats
texture(GLsizei size, GLsizei mips, 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) : _handle(NULL)
, _width(size), _height(size), _depth(1) , _width(size), _height(size), _depth(1)
, _samples(1), _mips(mips) { , _samples(1), _mips(mips) {
@@ -247,6 +247,9 @@ public:
glTexSubImage2D(base_cubemap_face + i, 0, 0, 0, _width, _height, layout, component, faces[i]); glTexSubImage2D(base_cubemap_face + i, 0, 0, 0, _width, _height, layout, component, faces[i]);
} }
} else if constexpr(compressed) { } 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 { } else {
for (int i = 0; i < cubemap_faces; ++i) { for (int i = 0; i < cubemap_faces; ++i) {
glTexImage2D(base_cubemap_face + i, 0, format, _width, _height, 0, layout, component, 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 /// \details Requires OES_texture_cube_map_array
texture(GLsizei size, GLsizei depth, GLsizei mips, 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) : _handle(NULL)
, _width(size), _height(size), _depth(depth) , _width(size), _height(size), _depth(depth)
, _samples(1), _mips(mips) { , _samples(1), _mips(mips) {
@@ -276,6 +279,9 @@ public:
glTexStorage3D(type, _mips, format, _width, _height, _depth * 6); glTexStorage3D(type, _mips, format, _width, _height, _depth * 6);
glTexSubImage3D(type, 0, 0, 0, 0, _width, _height, _depth * 6, layout, component, data); glTexSubImage3D(type, 0, 0, 0, 0, _width, _height, _depth * 6, layout, component, data);
} else if constexpr(compressed) { } else if constexpr(compressed) {
for (int i = 0; i < cubemap_faces; ++i) {
glCompressedTexImage3D(type, 0, format, _width, _height, _depth * 6, 0, bytes, data);
}
} else { } else {
glTexImage3D(type, 0, format, _width, _height, _depth * 6, 0, layout, component, data); glTexImage3D(type, 0, format, _width, _height, _depth * 6, 0, layout, component, data);
} }

View File

@@ -102,6 +102,11 @@ private:
} }
public: public:
template<typename T>
static type_data* get_data() {
return nullptr;
}
template<typename T> template<typename T>
static type_data* get_data() requires(not is_void_v<T>) { static type_data* get_data() requires(not is_void_v<T>) {
auto& typelist = _typelist(); auto& typelist = _typelist();
@@ -118,11 +123,6 @@ public:
return typelist[uuid].get(); return typelist[uuid].get();
} }
template<typename T>
static type_data* get_data() {
return nullptr;
}
template<typename...Ts> template<typename...Ts>
static dynarray<type_data*> get_data(typelist<Ts...>) { static dynarray<type_data*> get_data(typelist<Ts...>) {
return { return {

View File

@@ -42,15 +42,15 @@ namespace fennec::detail
template<typename FuncT, typename...ArgsT> using thread_proxy_t = tuple<FuncT, ArgsT...>; template<typename FuncT, typename...ArgsT> using thread_proxy_t = tuple<FuncT, ArgsT...>;
template<typename FuncT, typename...ArgsT, size_t...I> template<typename FuncT, typename...ArgsT, size_t...I>
void _execute_thread(thread_proxy_t<function<FuncT>, ArgsT...>& data, index_metasequence<I...>) { void _execute_thread(thread_proxy_t<FuncT, ArgsT...>& data, index_metasequence<I...>) {
auto func = data.template get<0>(); auto func = fennec::move(fennec::get<0>(data));
func(fennec::move(data.template get<I + 1>()) ...); func(fennec::move(fennec::get<I + 1>(data)) ...);
} }
template<typename DataT> template<typename DataT>
void* _run_thread(void* data) { void* _run_thread(void* data) {
unique_ptr<DataT> ptr(data); unique_ptr<DataT> ptr(static_cast<DataT*>(data));
detail::_execute_thread(*ptr, make_index_metasequence_t<DataT::size - 1>{}); detail::_execute_thread(*ptr.get(), make_index_metasequence_t<DataT::size - 1>{});
return nullptr; return nullptr;
} }

View File

@@ -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 <https://www.gnu.org/licenses/>.
// =====================================================================================================================
///
/// \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<typename MutexT>
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

View File

@@ -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 <https://www.gnu.org/licenses/>.
// =====================================================================================================================
///
/// \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 <pthread.h>
#include <errno.h>
#include <fennec/lang/assert.h>
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

View File

@@ -33,12 +33,13 @@
#define FENNEC_THREADING_THREAD_H #define FENNEC_THREADING_THREAD_H
#include <pthread.h> #include <pthread.h>
#include <fennec/format/formatter.h>
#include <fennec/lang/type_traits.h>
#include <fennec/lang/type_transforms.h>
#include <fennec/memory/bytes.h> #include <fennec/memory/bytes.h>
#include <fennec/threading/detail/_thread.h> #include <fennec/threading/detail/_thread.h>
#include <fennec/format/formatter.h>
namespace fennec namespace fennec
{ {
@@ -47,7 +48,7 @@ private:
static constexpr pthread_t nullthread = 0; static constexpr pthread_t nullthread = 0;
public: public:
using native_handle_type = pthread_t; using handle_t = pthread_t;
class id { class id {
public: public:
@@ -89,10 +90,13 @@ public:
requires(not is_same_v<thread, remove_cvref_t<FuncT>>) requires(not is_same_v<thread, remove_cvref_t<FuncT>>)
thread(FuncT&& func, ArgsT&&...args) thread(FuncT&& func, ArgsT&&...args)
: _handle(nullthread) { : _handle(nullthread) {
using proxy_t = detail::thread_proxy_t<function<FuncT>, ArgsT...>; using proxy_t = detail::thread_proxy_t<decay_t<FuncT>, decay_t<ArgsT>...>;
unique_ptr<proxy_t> proxy = fennec::make_unique<proxy_t>(fennec::forward<FuncT>(func), fennec::forward<ArgsT>(args)...); unique_ptr<proxy_t> proxy = fennec::make_unique<proxy_t>(
fennec::decay_copy(fennec::forward<FuncT>(func)),
fennec::decay_copy(fennec::forward<ArgsT>(args))...
);
int_t res = ::pthread_create(&_handle, nullptr, &detail::_run_thread<proxy_t>(), proxy.get()); int_t res = ::pthread_create(&_handle, nullptr, &detail::_run_thread<proxy_t>, proxy.get());
assertf(res == 0, "Thread creation failed!"); assertf(res == 0, "Thread creation failed!");
proxy.release(); proxy.release();
@@ -108,7 +112,8 @@ public:
} }
~thread() { ~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 { bool joinable() const noexcept {
@@ -131,7 +136,7 @@ public:
} }
int_t res = pthread_detach(_handle); int_t res = pthread_detach(_handle);
assertf(res == 0, "Failed to detatch thread!"); assertf(res == 0, "Failed to detach thread!");
_handle = nullthread; _handle = nullthread;
} }
@@ -155,14 +160,14 @@ private:
template <> template <>
struct hash<thread::id> { struct hash<thread::id> {
size_t operator()(thread::id v) const noexcept { size_t operator()(thread::id v) const noexcept {
return hash<thread::native_handle_type>()(v._id); return hash<thread::handle_t>()(v._id);
} }
}; };
template<> template<>
struct formatter<thread::id> { struct formatter<thread::id> {
string operator()(const format_arg& fmt, thread::id v) const noexcept { string operator()(const format_arg& fmt, thread::id v) const noexcept {
return formatter<thread::native_handle_type>()(fmt, v._id); return formatter<thread::handle_t>()(fmt, v._id);
} }
}; };

View File

@@ -33,13 +33,62 @@
#define FENNEC_TEST_THREADING_H #define FENNEC_TEST_THREADING_H
#include <fennec/threading/atomic.h> #include <fennec/threading/atomic.h>
#include <fennec/threading/lock_guard.h>
#include <fennec/threading/mutex.h>
#include <fennec/threading/thread.h>
namespace fennec::test namespace fennec::test
{ {
inline void fennec_test_threading_test_atomic(atomic<size_t>* test_atomic, size_t N = 1000) {
for (size_t i = 0; i < N; ++i) {
++*test_atomic;
}
}
template<size_t ThreadsV>
inline void fennec_test_threading_run_test_atomic(array<thread, ThreadsV>& threads, size_t N = 1000) {
atomic<size_t> 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<size_t ThreadsV>
inline void fennec_test_threading_run_test_mutex(array<thread, ThreadsV>& 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() { inline void fennec_test_threading() {
atomic<bool> test; static constexpr size_t N = 1000, threads = 4;
array<thread, threads> arr;
fennec_test_threading_run_test_atomic(arr, N);
fennec_test_threading_run_test_mutex(arr, N);
} }