diff --git a/core/os/condition_variable.h b/core/os/condition_variable.h new file mode 100644 index 000000000000..6037ff327dd7 --- /dev/null +++ b/core/os/condition_variable.h @@ -0,0 +1,60 @@ +/**************************************************************************/ +/* condition_variable.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef CONDITION_VARIABLE_H +#define CONDITION_VARIABLE_H + +#include + +// An object one or multiple threads can wait on a be notified by some other. +// Normally, you want to use a semaphore for such scenarios, but when the +// condition is something different than a count being greater than zero +// (which is the built-in logic in a semaphore) or you want to provide your +// own mutex to tie the wait-notify to some other behavior, you need to use this. + +class ConditionVariable { + mutable std::condition_variable condition; + +public: + template + _ALWAYS_INLINE_ void wait(const MutexLock &p_lock) const { + condition.wait(const_cast &>(p_lock.lock)); + } + + _ALWAYS_INLINE_ void notify_one() const { + condition.notify_one(); + } + + _ALWAYS_INLINE_ void notify_all() const { + condition.notify_all(); + } +}; + +#endif // CONDITION_VARIABLE_H diff --git a/core/os/mutex.h b/core/os/mutex.h index ceedcb235a2f..90cc1632e8fb 100644 --- a/core/os/mutex.h +++ b/core/os/mutex.h @@ -31,12 +31,20 @@ #ifndef MUTEX_H #define MUTEX_H +#include "core/error/error_macros.h" #include "core/typedefs.h" #include +template +class MutexLock; + template class MutexImpl { + friend class MutexLock>; + + using StdMutexType = StdMutexT; + mutable StdMutexT mutex; public: @@ -53,18 +61,65 @@ public: } }; +// A very special kind of mutex, used in scenarios where these +// requirements hold at the same time: +// - Must be used with a condition variable (only binary mutexes are suitable). +// - Must have recursive semnantics (or simulate, as this one does). +// The implementation keeps the lock count in TS. Therefore, only +// one object of each version of the template can exists; hence the Tag argument. +// Tags must be unique across the Godot codebase. +// Also, don't forget to declare the thread_local variable on each use. +template +class SafeBinaryMutex { + friend class MutexLock; + + using StdMutexType = std::mutex; + + mutable std::mutex mutex; + static thread_local uint32_t count; + +public: + _ALWAYS_INLINE_ void lock() const { + if (++count == 1) { + mutex.lock(); + } + } + + _ALWAYS_INLINE_ void unlock() const { + DEV_ASSERT(count); + if (--count == 0) { + mutex.unlock(); + } + } + + _ALWAYS_INLINE_ bool try_lock() const { + if (count) { + count++; + return true; + } else { + if (mutex.try_lock()) { + count++; + return true; + } else { + return false; + } + } + } + + ~SafeBinaryMutex() { + DEV_ASSERT(!count); + } +}; + template class MutexLock { - const MutexT &mutex; + friend class ConditionVariable; + + std::unique_lock lock; public: _ALWAYS_INLINE_ explicit MutexLock(const MutexT &p_mutex) : - mutex(p_mutex) { - mutex.lock(); - } - - _ALWAYS_INLINE_ ~MutexLock() { - mutex.unlock(); + lock(p_mutex.mutex) { } };