Threading classes wrappers

master
Marc Gilleron 2022-04-19 00:32:37 +01:00
parent f48ebe8557
commit 4e3d4d4ed8
22 changed files with 293 additions and 42 deletions

2
SCsub
View File

@ -38,6 +38,8 @@ voxel_files = [
"util/noise/fast_noise_lite/*.cpp",
"util/noise/gd_noise_range.cpp",
"util/thread/*.cpp",
"util/tasks/*.cpp",
"util/tasks/godot/*.cpp",

View File

@ -1,9 +1,11 @@
#ifndef VOXEL_GENERATOR_GRAPH_H
#define VOXEL_GENERATOR_GRAPH_H
#include "../../util/thread/rw_lock.h"
#include "../voxel_generator.h"
#include "program_graph.h"
#include "voxel_graph_runtime.h"
#include <memory>
class Image;

View File

@ -1,8 +1,10 @@
#ifndef VOXEL_MESHER_BLOCKY_H
#define VOXEL_MESHER_BLOCKY_H
#include "../../util/thread/rw_lock.h"
#include "../voxel_mesher.h"
#include "voxel_blocky_library.h"
#include <core/object/ref_counted.h>
#include <scene/resources/mesh.h>
#include <vector>

View File

@ -3,6 +3,7 @@
#include "../../util/math/vector2f.h"
#include "../../util/math/vector3f.h"
#include "../../util/thread/rw_lock.h"
#include "../voxel_mesher.h"
#include "voxel_color_palette.h"

View File

@ -12,8 +12,6 @@
#include "save_block_data_task.h"
#include <core/config/project_settings.h>
#include <core/os/memory.h>
#include <thread>
namespace zylann::voxel {
@ -38,7 +36,7 @@ void VoxelServer::destroy_singleton() {
VoxelServer::VoxelServer() {
CRASH_COND(ProjectSettings::get_singleton() == nullptr);
const int hw_threads_hint = std::thread::hardware_concurrency();
const int hw_threads_hint = Thread::get_hardware_concurrency();
ZN_PRINT_VERBOSE(format("Voxel: HW threads hint: {}", hw_threads_hint));
// Compute thread count for general pool.

View File

@ -288,6 +288,7 @@ struct VoxelFileLockerWrite {
String _path;
};
// Helper class to store tasks and schedule them in a single batch
class BufferedTaskScheduler {
public:
static BufferedTaskScheduler &get_for_current_thread();

View File

@ -5,10 +5,10 @@
#include "../util/fixed_array.h"
#include "../util/flat_map.h"
#include "../util/math/box3i.h"
#include "../util/thread/rw_lock.h"
#include "funcs.h"
#include "voxel_metadata.h"
#include <core/os/rw_lock.h>
#include <limits>
namespace zylann::voxel {

View File

@ -1,10 +1,7 @@
#include "voxel_memory_pool.h"
#include "../util/macros.h"
#include "../util/profiling.h"
#include <core/os/os.h>
#include <core/string/print_string.h>
#include <core/variant/variant.h>
#include "../util/string_funcs.h"
namespace zylann::voxel {
@ -33,7 +30,7 @@ VoxelMemoryPool::VoxelMemoryPool() {}
VoxelMemoryPool::~VoxelMemoryPool() {
#ifdef TOOLS_ENABLED
if (OS::get_singleton()->is_stdout_verbose()) {
if (is_verbose_output_enabled()) {
debug_print();
}
#endif
@ -142,28 +139,23 @@ void VoxelMemoryPool::clear() {
}
void VoxelMemoryPool::debug_print() {
print_line("-------- VoxelMemoryPool ----------");
println("-------- VoxelMemoryPool ----------");
for (unsigned int pot = 0; pot < _pot_pools.size(); ++pot) {
Pool &pool = _pot_pools[pot];
MutexLock lock(pool.mutex);
print_line(String("Pool {0}: {1} blocks (capacity {2})")
.format(varray(pot, ZN_SIZE_T_TO_VARIANT(pool.blocks.size()),
ZN_SIZE_T_TO_VARIANT(pool.blocks.capacity()))));
println(format("Pool {}: {} blocks (capacity {})", pot, pool.blocks.size(), pool.blocks.capacity()));
}
}
unsigned int VoxelMemoryPool::debug_get_used_blocks() const {
//MutexLock lock(_mutex);
return _used_blocks;
}
size_t VoxelMemoryPool::debug_get_used_memory() const {
//MutexLock lock(_mutex);
return _used_memory;
}
size_t VoxelMemoryPool::debug_get_total_memory() const {
//MutexLock lock(_mutex);
return _total_memory;
}

View File

@ -3,7 +3,7 @@
#include "../util/fixed_array.h"
#include "../util/math/funcs.h"
#include "core/os/mutex.h"
#include "../util/thread/mutex.h"
#include <atomic>
#include <limits>

View File

@ -2,6 +2,7 @@
#define VOXEL_STREAM_REGION_H
#include "../../util/fixed_array.h"
#include "../../util/thread/mutex.h"
#include "../file_utils.h"
#include "../voxel_stream.h"
#include "region_file.h"

View File

@ -1,10 +1,10 @@
#ifndef VOXEL_STREAM_SQLITE_H
#define VOXEL_STREAM_SQLITE_H
#include "../../util/thread/mutex.h"
#include "../voxel_block_serializer.h"
#include "../voxel_stream.h"
#include "../voxel_stream_cache.h"
#include <core/os/mutex.h>
#include <vector>
namespace zylann::voxel {

View File

@ -2,7 +2,9 @@
#define VOXEL_STREAM_H
#include "../util/memory.h"
#include "../util/thread/rw_lock.h"
#include "instance_data.h"
#include <core/io/resource.h>
namespace zylann::voxel {

View File

@ -12,6 +12,8 @@
#include "../../util/profiling_clock.h"
#include "../../util/string_funcs.h"
#include "../../util/tasks/async_dependency_tracker.h"
#include "../../util/thread/mutex.h"
#include "../../util/thread/rw_lock.h"
#include "../instancing/voxel_instancer.h"
#include "voxel_lod_terrain_update_task.h"

View File

@ -2,10 +2,9 @@
#define ZN_FILE_LOCKER_H
#include "errors.h"
#include "thread/mutex.h"
#include "thread/rw_lock.h"
#include <core/io/file_access.h>
#include <core/os/mutex.h>
#include <core/os/rw_lock.h>
#include <core/templates/hash_map.h>
namespace zylann {

View File

@ -1,7 +1,7 @@
#include "threaded_task_runner.h"
#include "../profiling.h"
#include "../string_funcs.h"
#include <core/os/os.h>
#include <core/os/time.h>
namespace zylann {
@ -32,8 +32,8 @@ void ThreadedTaskRunner::create_thread(ThreadData &d, uint32_t i) {
d.stop = false;
d.waiting = false;
d.index = i;
if (!_name.is_empty()) {
d.name = String("{0} {1}").format(varray(_name, i));
if (!_name.empty()) {
d.name = format("{} {}", _name, i);
}
d.thread.start(thread_func_static, &d);
}
@ -57,7 +57,7 @@ void ThreadedTaskRunner::destroy_all_threads() {
}
}
void ThreadedTaskRunner::set_name(String name) {
void ThreadedTaskRunner::set_name(const char *name) {
_name = name;
}
@ -121,12 +121,11 @@ void ThreadedTaskRunner::thread_func_static(void *p_data) {
ThreadData &data = *static_cast<ThreadData *>(p_data);
ThreadedTaskRunner &pool = *data.pool;
if (!data.name.is_empty()) {
Thread::set_name(data.name);
if (!data.name.empty()) {
Thread::set_name(data.name.c_str());
#ifdef ZN_PROFILER_ENABLED
CharString thread_name = data.name.utf8();
ZN_PROFILE_SET_THREAD_NAME(thread_name.get_data());
ZN_PROFILE_SET_THREAD_NAME(data.name.c_str());
#endif
}
@ -251,7 +250,7 @@ void ThreadedTaskRunner::wait_for_all_tasks() {
}
}
OS::get_singleton()->delay_usec(2000);
Thread::sleep_usec(2000);
if (!error1_reported && Time::get_singleton()->get_ticks_msec() - before > suspicious_delay_msec) {
ZN_PRINT_WARNING("Waiting for all tasks to be picked is taking a long time");
@ -274,7 +273,7 @@ void ThreadedTaskRunner::wait_for_all_tasks() {
}
}
OS::get_singleton()->delay_usec(2000);
Thread::sleep_usec(2000);
if (!error2_reported && Time::get_singleton()->get_ticks_msec() - before > suspicious_delay_msec) {
ZN_PRINT_WARNING("Waiting for all tasks to be completed is taking a long time");

View File

@ -3,16 +3,13 @@
#include "../fixed_array.h"
#include "../span.h"
#include "../thread/mutex.h"
#include "../thread/semaphore.h"
#include "../thread/thread.h"
#include "threaded_task.h"
#include <core/os/mutex.h>
#include <core/os/semaphore.h>
#include <core/os/thread.h>
#include <core/string/ustring.h>
#include <queue>
class Thread;
#include <string>
namespace zylann {
@ -33,7 +30,7 @@ public:
// Set name prefix to recognize threads of this pool in debug tools.
// Must be called before configuring thread count.
void set_name(String name);
void set_name(const char *name);
// TODO Add ability to change it while running without skipping tasks
// Can't be changed after tasks have been queued
@ -92,7 +89,7 @@ private:
bool stop = false;
bool waiting = false;
State debug_state = STATE_STOPPED;
String name;
std::string name;
void wait_to_finish_and_reset() {
thread.wait_to_finish();
@ -125,7 +122,7 @@ private:
uint32_t _batch_count = 1;
uint32_t _priority_update_period = 32;
String _name;
std::string _name;
unsigned int _debug_received_tasks = 0;
unsigned int _debug_completed_tasks = 0;

View File

@ -2,7 +2,7 @@
#define ZYLANN_TIME_SPREAD_TASK_RUNNER_H
#include "../span.h"
#include <core/os/mutex.h>
#include "../thread/mutex.h"
#include <cstdint>
#include <queue>

51
util/thread/mutex.h Normal file
View File

@ -0,0 +1,51 @@
#ifndef ZN_MUTEX_H
#define ZN_MUTEX_H
#include <mutex>
namespace zylann {
template <class StdMutexT>
class MutexImpl {
mutable StdMutexT mutex;
public:
inline void lock() const {
mutex.lock();
}
inline void unlock() const {
mutex.unlock();
}
inline bool try_lock() const {
return mutex.try_lock();
}
};
template <class MutexT>
class MutexLock {
const MutexT &mutex;
public:
inline explicit MutexLock(const MutexT &p_mutex) : mutex(p_mutex) {
mutex.lock();
}
inline ~MutexLock() {
mutex.unlock();
}
};
using Mutex = MutexImpl<std::recursive_mutex>; // Recursive, for general use
using BinaryMutex = MutexImpl<std::mutex>; // Non-recursive, handle with care
// Don't instantiate these templates in every file where they are used, do it just once
extern template class MutexImpl<std::recursive_mutex>;
extern template class MutexImpl<std::mutex>;
extern template class MutexLock<MutexImpl<std::recursive_mutex>>;
extern template class MutexLock<MutexImpl<std::mutex>>;
} // namespace zylann
#endif // ZN_MUTEX_H

72
util/thread/rw_lock.h Normal file
View File

@ -0,0 +1,72 @@
#ifndef ZN_RW_LOCK_H
#define ZN_RW_LOCK_H
#include <shared_mutex>
namespace zylann {
class RWLock {
public:
// Lock the rwlock, block if locked by someone else
void read_lock() const {
_mutex.lock_shared();
}
// Unlock the rwlock, let other threads continue
void read_unlock() const {
_mutex.unlock_shared();
}
// Attempt to lock the rwlock, returns `true` on success, `false` means it can't lock.
bool read_try_lock() const {
return _mutex.try_lock_shared();
}
// Lock the rwlock, block if locked by someone else
void write_lock() {
_mutex.lock();
}
// Unlock the rwlock, let other thwrites continue
void write_unlock() {
_mutex.unlock();
}
// Attempt to lock the rwlock, returns `true` on success, `false` means it can't lock.
bool write_try_lock() {
return _mutex.try_lock();
}
private:
mutable std::shared_timed_mutex _mutex;
};
class RWLockRead {
public:
RWLockRead(const RWLock &p_lock) : _lock(p_lock) {
_lock.read_lock();
}
~RWLockRead() {
_lock.read_unlock();
}
private:
const RWLock &_lock;
};
class RWLockWrite {
public:
RWLockWrite(RWLock &p_lock) : _lock(p_lock) {
_lock.write_lock();
}
~RWLockWrite() {
_lock.write_unlock();
}
private:
RWLock &_lock;
};
} // namespace zylann
#endif // ZN_RW_LOCK_H

42
util/thread/semaphore.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef ZN_SEMAPHORE_H
#define ZN_SEMAPHORE_H
#include <condition_variable>
#include <mutex>
namespace zylann {
class Semaphore {
public:
inline void post() const {
std::lock_guard<decltype(_mutex)> lock(_mutex);
++_count;
_condition.notify_one();
}
inline void wait() const {
std::unique_lock<decltype(_mutex)> lock(_mutex);
while (_count != 0) { // Handle spurious wake-ups.
_condition.wait(lock);
}
--_count;
}
inline bool try_wait() const {
std::lock_guard<decltype(_mutex)> lock(_mutex);
if (_count != 0) {
--_count;
return true;
}
return false;
}
private:
mutable std::mutex _mutex;
mutable std::condition_variable _condition;
mutable unsigned long _count = 0; // Initialized as locked.
};
} // namespace zylann
#endif // ZN_SEMAPHORE_H

48
util/thread/thread.cpp Normal file
View File

@ -0,0 +1,48 @@
#include "thread.h"
#include "../memory.h"
#include <core/os/os.h>
#include <core/os/thread.h>
#include <core/string/ustring.h>
namespace zylann {
struct ThreadImpl {
::Thread thread;
};
Thread::Thread() {
_impl = ZN_NEW(ThreadImpl);
}
Thread::~Thread() {
ZN_DELETE(_impl);
}
void Thread::start(Callback p_callback, void *p_userdata, Priority priority) {
::Thread::Settings settings;
settings.priority = ::Thread::Priority(priority);
_impl->thread.start(p_callback, p_userdata, settings);
}
bool Thread::is_started() const {
return _impl->thread.is_started();
}
void Thread::wait_to_finish() {
_impl->thread.wait_to_finish();
}
void Thread::set_name(const char *name) {
::Thread::set_name(String(name));
}
void Thread::sleep_usec(uint32_t microseconds) {
OS::get_singleton()->delay_usec(microseconds);
}
unsigned int Thread::get_hardware_concurrency() {
return std::thread::hardware_concurrency();
}
} // namespace zylann

40
util/thread/thread.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef ZN_THREAD_H
#define ZN_THREAD_H
#include <cstdint>
namespace zylann {
struct ThreadImpl;
class Thread {
public:
enum Priority { //
PRIORITY_LOW,
PRIORITY_NORMAL,
PRIORITY_HIGH
};
typedef void (*Callback)(void *p_userdata);
Thread();
~Thread();
void start(Callback p_callback, void *p_userdata, Priority priority = PRIORITY_NORMAL);
bool is_started() const;
void wait_to_finish();
// Gets a hint of the number of concurrent threads natively supported
static unsigned int get_hardware_concurrency();
// Targets the current thread
static void set_name(const char *name);
static void sleep_usec(uint32_t microseconds);
private:
ThreadImpl *_impl = nullptr;
};
} // namespace zylann
#endif // ZN_THREAD_H