Merge branch 'develop' into persist
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 32 KiB |
@ -0,0 +1,32 @@
|
||||
#include <regex>
|
||||
|
||||
namespace DFHack {
|
||||
namespace neverCalled {
|
||||
|
||||
/**
|
||||
* gcc/linstdc++ seems to generate code that links libstdc++ back to first
|
||||
* shared object using std::regex. To allow plugins unload with std::regex in
|
||||
* the code we need the std::regex functions inside libdfhack.so.
|
||||
*
|
||||
* If your plugin decides to use any overloads that aren't listed here it may
|
||||
* stay in memory after dlclose.
|
||||
*/
|
||||
std::regex stdRegexPluginUnloadWorkaround()
|
||||
{
|
||||
std::regex fake("foo");
|
||||
std::string haystack("bar is foo in the world");
|
||||
std::regex fake2(std::string("bar"));
|
||||
if (std::regex_match(haystack, fake))
|
||||
std::swap(fake, fake2);
|
||||
if (std::regex_search(haystack, fake))
|
||||
std::swap(fake, fake2);
|
||||
const char* haystack2 = "foo";
|
||||
if (std::regex_match(haystack2, fake))
|
||||
std::swap(fake, fake2);
|
||||
if (std::regex_search(haystack2, fake))
|
||||
std::swap(fake, fake2);
|
||||
return fake;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
/**
|
||||
Copyright © 2018 Pauli <suokkos@gmail.com>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product
|
||||
documentation would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "Core.h"
|
||||
|
||||
#include "Debug.h"
|
||||
#include "DebugManager.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <thread>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
static tm* localtime_r(const time_t* time, tm* result)
|
||||
{
|
||||
localtime_s(result, time);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace DFHack {
|
||||
DBG_DECLARE(core,debug);
|
||||
|
||||
void DebugManager::registerCategory(DebugCategory& cat)
|
||||
{
|
||||
DEBUG(debug) << "register DebugCategory '" << cat.category()
|
||||
<< "' from '" << cat.plugin()
|
||||
<< "' allowed " << cat.allowed() << std::endl;
|
||||
std::lock_guard<std::mutex> guard(access_mutex_);
|
||||
push_back(&cat);
|
||||
categorySignal(CAT_ADD, cat);
|
||||
}
|
||||
|
||||
void DebugManager::unregisterCategory(DebugCategory& cat)
|
||||
{
|
||||
DEBUG(debug) << "unregister DebugCategory '" << cat.category()
|
||||
<< "' from '" << cat.plugin()
|
||||
<< "' allowed " << cat.allowed() << std::endl;
|
||||
std::lock_guard<std::mutex> guard(access_mutex_);
|
||||
auto iter = std::find(begin(), end(), &cat);
|
||||
std::swap(*iter, back());
|
||||
pop_back();
|
||||
categorySignal(CAT_REMOVE, cat);
|
||||
}
|
||||
|
||||
DebugRegisterBase::DebugRegisterBase(DebugCategory* cat)
|
||||
{
|
||||
// Make sure Core lives at least as long any DebugCategory to
|
||||
// allow debug prints until all Debugcategories has been destructed
|
||||
Core::getInstance();
|
||||
DebugManager::getInstance().registerCategory(*cat);
|
||||
}
|
||||
|
||||
void DebugRegisterBase::unregister(DebugCategory* cat)
|
||||
{
|
||||
DebugManager::getInstance().unregisterCategory(*cat);
|
||||
}
|
||||
|
||||
static color_value selectColor(const DebugCategory::level msgLevel)
|
||||
{
|
||||
switch(msgLevel) {
|
||||
case DebugCategory::LTRACE:
|
||||
return COLOR_GREY;
|
||||
case DebugCategory::LDEBUG:
|
||||
return COLOR_LIGHTBLUE;
|
||||
case DebugCategory::LINFO:
|
||||
return COLOR_CYAN;
|
||||
case DebugCategory::LWARNING:
|
||||
return COLOR_YELLOW;
|
||||
case DebugCategory::LERROR:
|
||||
return COLOR_LIGHTRED;
|
||||
}
|
||||
return COLOR_WHITE;
|
||||
}
|
||||
|
||||
#if __GNUC__
|
||||
// Allow gcc to optimize tls access. It also makes sure initialized is done as
|
||||
// early as possible. The early initialization helps to give threads same ids as
|
||||
// gdb shows.
|
||||
#define EXEC_ATTR __attribute__((tls_model("initial-exec")))
|
||||
#else
|
||||
#define EXEC_ATTR
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
static std::atomic<uint32_t> nextId{0};
|
||||
static EXEC_ATTR thread_local uint32_t thread_id{nextId.fetch_add(1)+1};
|
||||
}
|
||||
|
||||
DebugCategory::ostream_proxy_prefix::ostream_proxy_prefix(
|
||||
const DebugCategory& cat,
|
||||
color_ostream& target,
|
||||
const DebugCategory::level msgLevel) :
|
||||
color_ostream_proxy(target)
|
||||
{
|
||||
color(selectColor(msgLevel));
|
||||
auto now = std::chrono::system_clock::now();
|
||||
tm local{};
|
||||
//! \todo c++ 2020 will have std::chrono::to_stream(fmt, system_clock::now())
|
||||
//! but none implements it yet.
|
||||
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
|
||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
|
||||
// Output time in format %02H:%02M:%02S.%03ms
|
||||
#if __GNUC__ < 5
|
||||
// Fallback for gcc 4
|
||||
char buffer[32];
|
||||
size_t sz = strftime(buffer, sizeof(buffer)/sizeof(buffer[0]),
|
||||
"%T.", localtime_r(&now_c, &local));
|
||||
*this << (sz > 0 ? buffer : "HH:MM:SS.")
|
||||
#else
|
||||
*this << std::put_time(localtime_r(&now_c, &local),"%T.")
|
||||
#endif
|
||||
<< std::setfill('0') << std::setw(3) << ms.count()
|
||||
// Thread id is allocated in the thread creation order to a thread_local
|
||||
// variable
|
||||
<< ":t" << thread_id
|
||||
// Output plugin and category names to make it easier to locate where
|
||||
// the message is coming. It would be easy replaces these with __FILE__
|
||||
// and __LINE__ passed from the macro if that would be preferred prefix.
|
||||
<< ':' << cat.plugin() << ':' << cat.category() << ": ";
|
||||
}
|
||||
|
||||
|
||||
DebugCategory::level DebugCategory::allowed() const noexcept
|
||||
{
|
||||
return allowed_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void DebugCategory::allowed(DebugCategory::level value) noexcept
|
||||
{
|
||||
level old = allowed_.exchange(value, std::memory_order_relaxed);
|
||||
if (old == value)
|
||||
return;
|
||||
TRACE(debug) << "modify DebugCategory '" << category()
|
||||
<< "' from '" << plugin()
|
||||
<< "' allowed " << value << std::endl;
|
||||
auto& manager = DebugManager::getInstance();
|
||||
manager.categorySignal(DebugManager::CAT_MODIFIED, *this);
|
||||
}
|
||||
|
||||
DebugCategory::cstring_ref DebugCategory::category() const noexcept
|
||||
{
|
||||
return category_;
|
||||
}
|
||||
|
||||
DebugCategory::cstring_ref DebugCategory::plugin() const noexcept
|
||||
{
|
||||
return plugin_;
|
||||
}
|
||||
|
||||
#if __cplusplus < 201703L && __cpp_lib_atomic_is_always_lock_free < 201603
|
||||
//! C++17 has std::atomic::is_always_lock_free for static_assert. Older
|
||||
//! standards only provide runtime checks if an atomic type is lock free
|
||||
struct failIfEnumAtomicIsNotLockFree {
|
||||
failIfEnumAtomicIsNotLockFree() {
|
||||
std::atomic<DebugCategory::level> test;
|
||||
if (test.is_lock_free())
|
||||
return;
|
||||
std::cerr << __FILE__ << ':' << __LINE__
|
||||
<< ": error: std::atomic<DebugCategory::level> should be lock free. Your compiler reports the atomic requires runtime locks. Either you are using a very old CPU or we need to change code to use integer atomic type." << std::endl;
|
||||
std::abort();
|
||||
}
|
||||
} failIfEnumAtomicIsNotLockFree;
|
||||
#endif
|
||||
|
||||
}
|
@ -0,0 +1,369 @@
|
||||
/**
|
||||
Copyright © 2018 Pauli <suokkos\gmail.com>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product
|
||||
documentation would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ColorText.h"
|
||||
|
||||
#include <atomic>
|
||||
#include "Core.h"
|
||||
|
||||
namespace DFHack {
|
||||
|
||||
/*! \file Debug.h
|
||||
* Light weight wrappers to runtime debug output filtering. Idea is to add as
|
||||
* little as possible code compared to debug output without filtering. The
|
||||
* effect is archived using #TRACE, #DEBUG, #INFO, #WARN and #ERR macros. They
|
||||
* "return" color_ostream object or reference that can be then used normally for
|
||||
* either printf or stream style debug output.
|
||||
*
|
||||
* Internally macros do inline filtering check which allows compiler to have a
|
||||
* fast path without debug output only checking unlikely condition. But if
|
||||
* output is enabled then runtime code will jump to debug printing function
|
||||
* calls. The macro setup code will also print standardized leading part of
|
||||
* debug string including time stamp, plugin name and debug category name.
|
||||
*
|
||||
* \code{.cpp}
|
||||
* #include "Debug.h"
|
||||
* DBG_DECLARE(myplugin,init);
|
||||
*
|
||||
* DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
* {
|
||||
* command_result rv = CR_OK;
|
||||
* DEBUG(init, out).print("initializing\n")
|
||||
* if ((rv = initWork()) != CR_OK) {
|
||||
* ERR(init, out) << "initWork failed with "
|
||||
* << rv << " error code" << std::endl;
|
||||
* return rv;
|
||||
* }
|
||||
* return rv
|
||||
* }
|
||||
* \endcode
|
||||
*
|
||||
* The debug print filtering levels can be changed using debugger. Following
|
||||
* gdb example would automatically setup core/init and core/render to trace
|
||||
* level when SDL_init is called.
|
||||
*
|
||||
* \code{.unparsed}
|
||||
* break SDL_init
|
||||
* commands
|
||||
* silent
|
||||
* p DFHack::debug::core::debug_init.allowed_ = 0
|
||||
* p DFHack::debug::core::debug_render.allowed_ = 0
|
||||
* c
|
||||
* end
|
||||
* \endcode
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __has_cpp_attribute
|
||||
#define __has_cpp_attribute(x) 0
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \defgroup debug_branch_prediction Branch prediction helper macros
|
||||
* Helper macro tells compiler that debug output branch is unlikely and should
|
||||
* be optimized to cold section of the function.
|
||||
* \{
|
||||
*/
|
||||
#if __cplusplus >= 202000L || __has_cpp_attribute(likely)
|
||||
// c++20 will have standard branch prediction hint attributes
|
||||
#define likely(x) (x) [[likely]]
|
||||
#define unlikely(x) (x) [[unlikely]]
|
||||
#elif defined(__GNUC__)
|
||||
// gcc has builtin functions that give hints to the branch prediction
|
||||
#define likely(x) (__builtin_expect(!!(x), 1))
|
||||
#define unlikely(x) (__builtin_expect(!!(x), 0))
|
||||
#else
|
||||
#define likely(x) (x)
|
||||
#define unlikely(x) (x)
|
||||
#endif
|
||||
//! \}
|
||||
|
||||
#ifdef NDEBUG
|
||||
//! Reduce minimum compiled in debug levels if NDEBUG is defined
|
||||
#define DBG_FILTER DFHack::DebugCategory::LINFO
|
||||
#else
|
||||
//! Set default compiled in debug levels to include all prints
|
||||
#define DBG_FILTER DFHack::DebugCategory::LTRACE
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* DebugCategory is used to enable and disable debug messages in runtime.
|
||||
* Declaration and definition are handled by #DBG_DECLARE and #DBG_DEFINE
|
||||
* macros. Runtime filtering support is handled by #TRACE, #DEBUG, #INFO, #WARN
|
||||
* and #ERR macros.
|
||||
*/
|
||||
class DFHACK_EXPORT DebugCategory final {
|
||||
public:
|
||||
//! type helper to maybe make it easier to convert to std::string_view when
|
||||
//! c++17 can be required.
|
||||
using cstring = const char*;
|
||||
using cstring_ref = const char*;
|
||||
/*!
|
||||
* Debug level enum for message filtering
|
||||
*/
|
||||
enum level {
|
||||
LTRACE = 0,
|
||||
LDEBUG = 1,
|
||||
LINFO = 2,
|
||||
LWARNING = 3,
|
||||
LERROR = 4,
|
||||
};
|
||||
|
||||
/*!
|
||||
* \param plugin the name of plugin the category belongs to
|
||||
* \param category the name of category
|
||||
* \param defaultLevel optional default filtering level for the category
|
||||
*/
|
||||
constexpr DebugCategory(cstring_ref plugin,
|
||||
cstring_ref category,
|
||||
level defaultLevel = LWARNING) noexcept :
|
||||
plugin_{plugin},
|
||||
category_{category},
|
||||
allowed_{defaultLevel}
|
||||
{}
|
||||
|
||||
|
||||
DebugCategory(const DebugCategory&) = delete;
|
||||
DebugCategory(DebugCategory&&) = delete;
|
||||
DebugCategory& operator=(DebugCategory) = delete;
|
||||
DebugCategory& operator=(DebugCategory&&) = delete;
|
||||
|
||||
/*!
|
||||
* Used by debug macros to check if message should be printed.
|
||||
*
|
||||
* It is defined in the header to allow compiler inline it and make disabled
|
||||
* state a fast path without function calls.
|
||||
*
|
||||
* \param msgLevel the debug message level the following print belongs to
|
||||
* \return boolean with true indicating that message should be printed
|
||||
*/
|
||||
bool isEnabled(const level msgLevel) const noexcept {
|
||||
const uint32_t intLevel = static_cast<uint32_t>(msgLevel);
|
||||
// Compile time filtering to allow compiling out debug checks prints
|
||||
// from binary.
|
||||
return static_cast<uint32_t>(DBG_FILTER) <= intLevel &&
|
||||
// Runtime filtering for debug messages
|
||||
static_cast<uint32_t>(allowed_.load(std::memory_order_relaxed)) <= intLevel;
|
||||
}
|
||||
|
||||
struct DFHACK_EXPORT ostream_proxy_prefix : public color_ostream_proxy {
|
||||
ostream_proxy_prefix(const DebugCategory& cat,
|
||||
color_ostream& target,
|
||||
DebugCategory::level level);
|
||||
~ostream_proxy_prefix()
|
||||
{
|
||||
flush();
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Fetch a steam object proxy object for output. It also adds standard
|
||||
* message components like time and plugin and category names to the line.
|
||||
*
|
||||
* User must make sure that the line is terminated with a line end.
|
||||
*
|
||||
* \param msgLevel Specifies the level which next debug message belongs
|
||||
* \return color_ostream_proxy that can be used to print the message
|
||||
* \sa DFHack::Core::getConsole()
|
||||
*/
|
||||
ostream_proxy_prefix getStream(const level msgLevel) const
|
||||
{
|
||||
return {*this,Core::getInstance().getConsole(),msgLevel};
|
||||
}
|
||||
/*!
|
||||
* Add standard message components to existing output stream object to begin
|
||||
* a new message line to an shared buffered object.
|
||||
*
|
||||
* \param msgLevel Specifies the level which next debug message belongs
|
||||
* \param target An output stream object where a debug output is printed
|
||||
* \return color_ostream reference that was passed as second parameter
|
||||
*/
|
||||
ostream_proxy_prefix getStream(const level msgLevel, color_ostream& target) const
|
||||
{
|
||||
return {*this,target,msgLevel};
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Allow management code to set a new filtering level
|
||||
* Caller must have locked DebugManager::access_mutex_.
|
||||
*/
|
||||
void allowed(level value) noexcept;
|
||||
//! Query current filtering level
|
||||
level allowed() const noexcept;
|
||||
//! Query plugin name
|
||||
cstring_ref plugin() const noexcept;
|
||||
//! Query category name
|
||||
cstring_ref category() const noexcept;
|
||||
private:
|
||||
|
||||
cstring plugin_;
|
||||
cstring category_;
|
||||
std::atomic<level> allowed_;
|
||||
#if __cplusplus >= 201703L || __cpp_lib_atomic_is_always_lock_free >= 201603
|
||||
static_assert(std::atomic<level>::is_always_lock_free,
|
||||
"std::atomic<level> should be lock free. You are using a very old CPU or code needs to use std::atomic<int>");
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle actual registering wrong template parameter generated pointer
|
||||
* calculation.
|
||||
*/
|
||||
class DFHACK_EXPORT DebugRegisterBase {
|
||||
protected:
|
||||
DebugRegisterBase(DebugCategory* category);
|
||||
void unregister(DebugCategory* category);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register DebugCategory to DebugManager
|
||||
*/
|
||||
template<DebugCategory* category>
|
||||
class DebugRegister final : public DebugRegisterBase {
|
||||
public:
|
||||
DebugRegister() :
|
||||
DebugRegisterBase{category}
|
||||
{}
|
||||
~DebugRegister() {
|
||||
unregister(category);
|
||||
}
|
||||
};
|
||||
|
||||
#define DBG_NAME(category) debug_ ## category
|
||||
|
||||
|
||||
/*!
|
||||
* Declares a debug category. There must be only a declaration per category.
|
||||
* Declaration should be in same plugin where it is used. If same category name
|
||||
* is used in core and multiple plugins they all are changed with same command
|
||||
* unless user specifies explicitly plugin name.
|
||||
*
|
||||
* Must be used in one translation unit only.
|
||||
*
|
||||
* \param plugin the name of plugin where debug category is used
|
||||
* \param category the name of category
|
||||
* \param level the initial DebugCategory::level filtering level.
|
||||
*/
|
||||
#define DBG_DECLARE(plugin,category, ...) \
|
||||
namespace debug { namespace plugin { \
|
||||
DebugCategory DBG_NAME(category){#plugin,#category,__VA_ARGS__}; \
|
||||
DebugRegister<&DBG_NAME(category)> register_ ## category; \
|
||||
} } \
|
||||
using debug::plugin::DBG_NAME(category)
|
||||
|
||||
/*!
|
||||
* Can be used to access a shared DBG_DECLARE category. But may not be used from
|
||||
* static initializer because translation unit order is undefined.
|
||||
*
|
||||
* Can be used in shared headers to gain access to one definition from
|
||||
* DBG_DECLARE.
|
||||
* \param plugin The plugin name that must match DBG_DECLARE
|
||||
* \param category The category name that must matnch DBG_DECLARE
|
||||
*/
|
||||
#define DBG_EXTERN(plugin,category) \
|
||||
namespace debug { namespace plugin { \
|
||||
extern DFHack::DebugCategory DBG_NAME(category); \
|
||||
} } \
|
||||
using debug::plugin::DBG_NAME(category)
|
||||
|
||||
#define DBG_PRINT(category,pred,level,...) \
|
||||
if pred(!DFHack::DBG_NAME(category).isEnabled(level)) \
|
||||
; /* nop fast path when debug category is disabled */ \
|
||||
else /* else to allow macro use in if-else branches */ \
|
||||
DFHack::DBG_NAME(category).getStream(level, ## __VA_ARGS__) \
|
||||
/* end of DBG_PRINT */
|
||||
|
||||
/*!
|
||||
* Open a line for trace level debug output if enabled
|
||||
*
|
||||
* Preferred category for inside loop debug messages or callbacks/methods called
|
||||
* multiple times per second. Good example would be render or onUpdate methods.
|
||||
*
|
||||
* \param category the debug category
|
||||
* \param optional the optional second parameter is an existing
|
||||
* color_ostream_proxy object
|
||||
* \return color_ostream object that can be used for stream output
|
||||
*/
|
||||
#define TRACE(category, ...) DBG_PRINT(category, likely, \
|
||||
DFHack::DebugCategory::LTRACE, ## __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
* Open a line for debug level debug output if enabled
|
||||
*
|
||||
* Preferred place to use it would be commonly called functions that don't fall
|
||||
* into trace category.
|
||||
*
|
||||
* \param category the debug category
|
||||
* \param optional the optional second parameter is an existing
|
||||
* color_ostream_proxy object
|
||||
* \return color_ostream object that can be used for stream output
|
||||
*/
|
||||
#define DEBUG(category, ...) DBG_PRINT(category, likely, \
|
||||
DFHack::DebugCategory::LDEBUG, ## __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
* Open a line for error level debug output if enabled
|
||||
*
|
||||
* Important debug messages when some rarely changed state changes. Example
|
||||
* would be when a debug category filtering level changes.
|
||||
*
|
||||
* \param category the debug category
|
||||
* \param optional the optional second parameter is an existing
|
||||
* color_ostream_proxy object
|
||||
* \return color_ostream object that can be used for stream output
|
||||
*/
|
||||
#define INFO(category, ...) DBG_PRINT(category, likely, \
|
||||
DFHack::DebugCategory::LINFO, ## __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
* Open a line for warning level debug output if enabled
|
||||
*
|
||||
* Warning category is for recoverable errors. This generally signals that
|
||||
* something unusual happened but there is code handling the error which should
|
||||
* allow df continue running without issues.
|
||||
*
|
||||
* \param category the debug category
|
||||
* \param optional the optional second parameter is an existing
|
||||
* color_ostream_proxy object
|
||||
* \return color_ostream object that can be used for stream output
|
||||
*/
|
||||
#define WARN(category, ...) DBG_PRINT(category, unlikely, \
|
||||
DFHack::DebugCategory::LWARNING, ## __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
* Open a line for error level error output if enabled
|
||||
*
|
||||
* Errors should be printed only for cases where plugin or dfhack can't recover
|
||||
* from reported error and it requires manual handling from the user.
|
||||
*
|
||||
* \param category the debug category
|
||||
* \param optional the optional second parameter is an existing
|
||||
* color_ostream_proxy object
|
||||
* \return color_ostream object that can be used for stream output
|
||||
*/
|
||||
#define ERR(category, ...) DBG_PRINT(category, unlikely, \
|
||||
DFHack::DebugCategory::LERROR, ## __VA_ARGS__)
|
||||
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/**
|
||||
Copyright © 2018 Pauli <suokkos@gmail.com>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product
|
||||
documentation would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Export.h"
|
||||
#include "Signal.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace DFHack {
|
||||
|
||||
/*! \file DebugManager.h
|
||||
* Expose an simple interface to runtime debug output filtering. The management
|
||||
* interface is separated from output interface because output is required in
|
||||
* many places while management is expected to be required only in a few places.
|
||||
*/
|
||||
|
||||
class DebugCategory;
|
||||
|
||||
/*!
|
||||
* \brief Container holding all registered runtime debug categories
|
||||
* Singleton DebugManager is a minor extension to std::vector that allows signal
|
||||
* callbacks to be attached from ui code that manages.
|
||||
*
|
||||
* To avoid parallel plugin unload causing issues access to DebugManager must be
|
||||
* protected by mutex. The access mutex will be taken when
|
||||
* DFHack::DebugCategory::~DebugCategory performs unregister calls to
|
||||
* DFHack::DebugManager. The mutex will protect from memory disappearing while
|
||||
* ui code is accessing or changing the runtime state.
|
||||
*
|
||||
* Signal emitting happens from a locked contexts. Taking the
|
||||
* DFHack::DebugManager::access_mutex_ in a signal callback will results to a
|
||||
* deadlock.
|
||||
*
|
||||
* The interface is extremely simple but enough to implement persistent filter
|
||||
* states and runtime configuration code in a plugin.
|
||||
*/
|
||||
class DFHACK_EXPORT DebugManager : public std::vector<DebugCategory*> {
|
||||
public:
|
||||
friend class DebugRegisterBase;
|
||||
|
||||
//! access_mutex_ protects all readers and writers to DFHack::DebugManager
|
||||
std::mutex access_mutex_;
|
||||
|
||||
//! Different signals that all will be routed to
|
||||
//! DebugManager::categorySignal
|
||||
enum signalType {
|
||||
CAT_ADD,
|
||||
CAT_REMOVE,
|
||||
CAT_MODIFIED,
|
||||
};
|
||||
|
||||
//! type to help access signal features like Connection and BlockGuard
|
||||
using categorySignal_t = Signal<void (signalType, DebugCategory&)>;
|
||||
|
||||
/*!
|
||||
* Signal object where callbacks can be connected. Connecting to a class
|
||||
* method can use a lambda wrapper to the capture object pointer and correctly
|
||||
* call required method.
|
||||
*
|
||||
* Signal is internally serialized allowing multiple threads call it
|
||||
* freely.
|
||||
*/
|
||||
categorySignal_t categorySignal;
|
||||
|
||||
//! Get the singleton object
|
||||
static DebugManager& getInstance() {
|
||||
static DebugManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
//! Prevent copies
|
||||
DebugManager(const DebugManager&) = delete;
|
||||
//! Prevent copies
|
||||
DebugManager(DebugManager&&) = delete;
|
||||
//! Prevent copies
|
||||
DebugManager& operator=(DebugManager) = delete;
|
||||
//! Prevent copies
|
||||
DebugManager& operator=(DebugManager&&) = delete;
|
||||
protected:
|
||||
DebugManager() = default;
|
||||
|
||||
//! Helper for automatic category registering and signaling
|
||||
void registerCategory(DebugCategory &);
|
||||
//! Helper for automatic category unregistering and signaling
|
||||
void unregisterCategory(DebugCategory &);
|
||||
private:
|
||||
};
|
||||
}
|
@ -0,0 +1,782 @@
|
||||
/**
|
||||
Copyright © 2018 Pauli <suokkos@gmail.com>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product
|
||||
documentation would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <climits>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#ifdef __SSE__
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
namespace DFHack {
|
||||
|
||||
/*!
|
||||
* Select inline implementation for Signal members
|
||||
* This requires careful destruction order where all connection has been
|
||||
* disconnected before Signal::~Signal()
|
||||
*/
|
||||
class signal_inline_tag;
|
||||
/*!
|
||||
* Select share_ptr managed implementation for Signal members.
|
||||
*
|
||||
* If Connection holding object may be deleted without full serialization
|
||||
* between disconnect and signal emit the holding object must be managed by
|
||||
* shared_ptr and derive from ConnectedBase. It will also have to pass the
|
||||
* std::shared_ptr<ConnectedBase> to connect.
|
||||
|
||||
* It uses two way std::weak_ptr reference to guarantee destruction of either
|
||||
* object doesn't happen when call is made to them.
|
||||
*
|
||||
* It is still possible to get a callback call after manual disconnect from
|
||||
* outside destructor. But without destruction risk the disconnect race can be
|
||||
* handled by slot implementation side.
|
||||
*/
|
||||
class signal_shared_tag;
|
||||
|
||||
/**
|
||||
* Used for signal_shared_tag holders that may race with destructor triggered
|
||||
* disconnect and emit from Signal.
|
||||
*/
|
||||
class ConnectedBase {
|
||||
};
|
||||
|
||||
template<typename Signature, typename tag = signal_inline_tag>
|
||||
class Signal;
|
||||
|
||||
namespace details {
|
||||
|
||||
template<typename Signature, typename tag>
|
||||
struct SignalImpl;
|
||||
|
||||
template<typename Signature, typename tag>
|
||||
struct selectImpl;
|
||||
|
||||
//! Manage callback states in thread safe manner
|
||||
template<typename Signature, typename tag>
|
||||
class CallbackHolderImpl;
|
||||
|
||||
template<typename RT, typename... Args>
|
||||
struct CallbackHolderBase {
|
||||
using Callback = std::function<RT(Args...)>;
|
||||
|
||||
CallbackHolderBase(const Callback& cb) :
|
||||
cb_{cb},
|
||||
state_{}
|
||||
{}
|
||||
|
||||
//! Block the connection
|
||||
void block() noexcept
|
||||
{
|
||||
state_ += blocked;
|
||||
}
|
||||
|
||||
//! Unblock the connection
|
||||
void unblock() noexcept
|
||||
{
|
||||
state_ -= blocked;
|
||||
}
|
||||
|
||||
//! Check if connection is deleted
|
||||
bool erased() const noexcept
|
||||
{
|
||||
return state_ & deleted;
|
||||
}
|
||||
|
||||
//! Check if connection is still active (not blocked or erased)
|
||||
operator bool() const noexcept
|
||||
{
|
||||
return !(state_ & ~inCall);
|
||||
}
|
||||
|
||||
protected:
|
||||
//! Immutable callback object
|
||||
const Callback cb_;
|
||||
using state_t = unsigned;
|
||||
//! Single shared state as a bitfield to simplify synchronization
|
||||
//! between state changes.
|
||||
std::atomic<state_t> state_;
|
||||
static constexpr state_t deleted = 0x1 << (sizeof(state_t)*CHAR_BIT - 1);
|
||||
static constexpr state_t inCall = deleted >> (sizeof(state_t)*CHAR_BIT/2);
|
||||
static constexpr state_t blocked = 0x1;
|
||||
static constexpr state_t blockedMask = inCall - 1;
|
||||
static constexpr state_t inCallMask = (deleted - 1) ^ blockedMask;
|
||||
};
|
||||
|
||||
template<typename RT, typename... Args>
|
||||
class CallbackHolderImpl<RT(Args...), signal_inline_tag> :
|
||||
public CallbackHolderBase<RT, Args...> {
|
||||
using parent_t = CallbackHolderBase<RT, Args...>;
|
||||
public:
|
||||
using Callback = typename parent_t::Callback;
|
||||
private:
|
||||
using state_t = typename parent_t::state_t;
|
||||
//! Make sure callback pointed object doesn't disappear under us
|
||||
//! while we call it.
|
||||
struct CallGuard {
|
||||
|
||||
//! Prevent copies but allow copy elision
|
||||
CallGuard(const CallGuard&);
|
||||
|
||||
//! Allow implicit conversion to callback for simply syntax
|
||||
const Callback& operator*() const noexcept
|
||||
{
|
||||
return holder_->cb_;
|
||||
}
|
||||
|
||||
operator bool() const noexcept
|
||||
{
|
||||
return *holder_;
|
||||
}
|
||||
|
||||
//! Mark call not to be called any more
|
||||
~CallGuard() {
|
||||
holder_->state_ -= parent_t::inCall;
|
||||
}
|
||||
private:
|
||||
//! Reference to the connection
|
||||
CallbackHolderImpl* holder_;
|
||||
|
||||
//! Mark call to be in process
|
||||
CallGuard(CallbackHolderImpl* holder) :
|
||||
holder_{holder}
|
||||
{
|
||||
holder_->state_ += parent_t::inCall;
|
||||
}
|
||||
//! Only allow construction from the CallbackHolderImpl::prepareCall
|
||||
friend class CallbackHolderImpl;
|
||||
};
|
||||
public:
|
||||
//! Construct the callback state for a callback
|
||||
CallbackHolderImpl(const Callback& cb) :
|
||||
parent_t{cb}
|
||||
{}
|
||||
|
||||
/*!
|
||||
* Data race free disconnection for the connection. It spins until
|
||||
* no more callers to wait. Spinning should be problem as callbacks
|
||||
* are expected to be simple and fast to execute.
|
||||
*
|
||||
* Must not be called from withing callback!
|
||||
*
|
||||
* \todo Maybe use monitor instruction to avoid busy wait and call
|
||||
* std::thread::yield() if wait is longer than expected.
|
||||
*/
|
||||
void erase() noexcept
|
||||
{
|
||||
state_t oldstate;
|
||||
state_t newstate;
|
||||
/** Spin until no callers to this callback */
|
||||
spin:
|
||||
while ((oldstate = parent_t::state_) & parent_t::inCallMask) {
|
||||
// pause would be portable to all old processors but there
|
||||
// isn't portable way to generate it without SSE header.
|
||||
#ifdef __SSE__
|
||||
_mm_pause();
|
||||
#endif
|
||||
}
|
||||
do {
|
||||
if (oldstate & parent_t::inCallMask)
|
||||
goto spin;
|
||||
newstate = oldstate | parent_t::deleted;
|
||||
} while(!parent_t::state_.compare_exchange_weak(oldstate, newstate));
|
||||
}
|
||||
|
||||
//! Return RAII CallGuard to protect race between callback and
|
||||
//! disconnect.
|
||||
CallGuard prepareCall()
|
||||
{
|
||||
return {this};
|
||||
}
|
||||
};
|
||||
|
||||
template<typename RT, typename... Args>
|
||||
class CallbackHolderImpl<RT(Args...), signal_shared_tag> :
|
||||
public CallbackHolderBase<RT, Args...> {
|
||||
using parent_t = CallbackHolderBase<RT, Args...>;
|
||||
public:
|
||||
using Callback = typename parent_t::Callback;
|
||||
private:
|
||||
using state_t = typename parent_t::state_t;
|
||||
//! Make sure callback pointed object doesn't disappear under us
|
||||
//! while we call it.
|
||||
struct CallGuard {
|
||||
|
||||
//! Prevent copies but allow copy elision
|
||||
CallGuard(const CallGuard&);
|
||||
|
||||
//! Allow implicit conversion to callback for simply syntax
|
||||
const Callback& operator*() const noexcept
|
||||
{
|
||||
return holder_->cb_;
|
||||
}
|
||||
|
||||
operator bool() const noexcept
|
||||
{
|
||||
// If this is not marked erased then weak_ref->lock succeeded or
|
||||
// the slot isn't managed by shared_ptr<ConnectedBase>
|
||||
return *holder_;
|
||||
}
|
||||
|
||||
private:
|
||||
//! Reference to the connection
|
||||
CallbackHolderImpl* holder_;
|
||||
std::shared_ptr<ConnectedBase> strong_ref_;
|
||||
|
||||
//! Mark call to be in process
|
||||
CallGuard(CallbackHolderImpl* holder) :
|
||||
holder_{holder},
|
||||
strong_ref_{holder->weak_ref_.lock()}
|
||||
{
|
||||
}
|
||||
//! Only allow construction from the CallbackHolderImpl::prepareCall
|
||||
friend class CallbackHolderImpl;
|
||||
};
|
||||
|
||||
std::weak_ptr<ConnectedBase> weak_ref_;
|
||||
friend CallGuard;
|
||||
public:
|
||||
//! Construct the callback state for an automatically synchronized object
|
||||
CallbackHolderImpl(const Callback& cb,
|
||||
std::shared_ptr<ConnectedBase>& ref) :
|
||||
parent_t{cb},
|
||||
weak_ref_{ref}
|
||||
{}
|
||||
|
||||
//! Construct the callback state for an externally synchronized object
|
||||
CallbackHolderImpl(const Callback& cb) :
|
||||
parent_t{cb},
|
||||
weak_ref_{}
|
||||
{}
|
||||
|
||||
/*!
|
||||
* erase from destructor can't happen while we are in call because
|
||||
*/
|
||||
void erase() noexcept
|
||||
{
|
||||
parent_t::state_ |= parent_t::deleted;
|
||||
}
|
||||
|
||||
//! Return RAII CallGuard to protect race between callback and
|
||||
//! disconnect.
|
||||
CallGuard prepareCall()
|
||||
{
|
||||
return {this};
|
||||
}
|
||||
};
|
||||
|
||||
template<typename RT, typename... Args, typename tag>
|
||||
struct SignalImpl<RT(Args...), tag> : public selectImpl<RT(Args...), tag>::parent_t {
|
||||
protected:
|
||||
using select_t = selectImpl<RT(Args...), tag>;
|
||||
using parent_t = typename select_t::parent_t;
|
||||
public:
|
||||
using CallbackHolder = CallbackHolderImpl<RT(Args...), tag>;
|
||||
using Callback = typename CallbackHolder::Callback;
|
||||
|
||||
//! The container type used to store callbacks
|
||||
using CallbackContainer = std::list<CallbackHolder>;
|
||||
struct BlockGuard;
|
||||
|
||||
//! Simple connection class that is required to disconnect from the
|
||||
//! signal.
|
||||
struct Connection {
|
||||
//! Construct a default Connection object but using it will result
|
||||
//! to undefined behavior unless proper connection is assigned to it
|
||||
Connection() = default;
|
||||
|
||||
Connection(Connection&& o) :
|
||||
iter_{o.iter_},
|
||||
signal_{}
|
||||
{
|
||||
std::swap(signal_, o.signal_);
|
||||
}
|
||||
|
||||
Connection& operator=(Connection&& o)
|
||||
{
|
||||
disconnect();
|
||||
iter_ = o.iter_;
|
||||
std::swap(signal_, o.signal_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Connection(const Connection&) = delete;
|
||||
Connection& operator=(const Connection&) = delete;
|
||||
|
||||
//! Disconnect from signal
|
||||
void disconnect()
|
||||
{
|
||||
auto s = select_t::lock(signal_);
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
s->disconnect(*this);
|
||||
}
|
||||
|
||||
~Connection()
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
private:
|
||||
//! Block the connection temporary
|
||||
void block()
|
||||
{
|
||||
auto s = select_t::lock(signal_);
|
||||
if (!s)
|
||||
return;
|
||||
iter_->block();
|
||||
}
|
||||
|
||||
//! Restore blocked connection
|
||||
void unblock()
|
||||
{
|
||||
auto s = select_t::lock(signal_);
|
||||
if (!s)
|
||||
return;
|
||||
iter_->unblock();
|
||||
}
|
||||
|
||||
//! Construct connection object
|
||||
Connection(const typename CallbackContainer::iterator &iter,
|
||||
typename select_t::weak_ptr ptr) :
|
||||
iter_{iter},
|
||||
signal_{ptr}
|
||||
{}
|
||||
|
||||
//! std::list iterator that is used to access the callback and allow
|
||||
//! removal from the list.
|
||||
typename CallbackContainer::iterator iter_;
|
||||
//! Reference to signal object
|
||||
typename select_t::weak_ptr signal_;
|
||||
friend SignalImpl;
|
||||
friend BlockGuard;
|
||||
};
|
||||
|
||||
/*!
|
||||
* BlockGuard allows temporary RAII guard managed blocking of a
|
||||
* connection object.
|
||||
*/
|
||||
struct BlockGuard {
|
||||
/*!
|
||||
* Block a connection that belongs to signal
|
||||
* \param connection The connection that will be temporary blocked
|
||||
*/
|
||||
BlockGuard(Connection& connection) :
|
||||
blocked_{&connection}
|
||||
{
|
||||
connection.block();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Unblock the temporary blocked connection
|
||||
*/
|
||||
~BlockGuard()
|
||||
{
|
||||
blocked_->unblock();
|
||||
}
|
||||
|
||||
//! Prevent copies but allow copy elision
|
||||
BlockGuard(const BlockGuard&);
|
||||
private:
|
||||
Connection* blocked_;
|
||||
};
|
||||
|
||||
Connection connect(const Callback& f)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(access_);
|
||||
auto iter = callbacks_.emplace(callbacks_.begin(), f);
|
||||
return {iter, parent_t::shared_from_this()};
|
||||
}
|
||||
|
||||
Connection connect(std::shared_ptr<ConnectedBase> c, const Callback& f)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(access_);
|
||||
auto iter = callbacks_.emplace(callbacks_.begin(), f, c);
|
||||
return {iter, parent_t::shared_from_this()};
|
||||
}
|
||||
|
||||
void disconnect(Connection& connection) {
|
||||
std::lock_guard<std::mutex> lock(access_);
|
||||
if (recursion_) {
|
||||
deleted_ = true;
|
||||
connection.iter_->erase();
|
||||
} else {
|
||||
callbacks_.erase(connection.iter_);
|
||||
}
|
||||
select_t::reset(connection.signal_);
|
||||
}
|
||||
|
||||
template<typename Combiner>
|
||||
void operator()(Combiner &combiner, Args&&... arg)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(access_);
|
||||
struct RecursionGuard {
|
||||
SignalImpl* signal_;
|
||||
std::unique_lock<std::mutex>* lock_;
|
||||
//! Increment access count to make sure disconnect doesn't erase
|
||||
RecursionGuard(SignalImpl *signal, std::unique_lock<std::mutex>* lock) :
|
||||
signal_{signal},
|
||||
lock_{lock}
|
||||
{
|
||||
++signal_->recursion_;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Clean up deleted functions in data race free and exception
|
||||
* safe manner.
|
||||
*/
|
||||
~RecursionGuard()
|
||||
{
|
||||
lock_->lock();
|
||||
if (--signal_->recursion_ == 0 && signal_->deleted_) {
|
||||
for (auto iter = signal_->callbacks_.begin(); iter != signal_->callbacks_.end();) {
|
||||
if (iter->erased())
|
||||
iter = signal_->callbacks_.erase(iter);
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
signal_->deleted_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
} guard{this, &lock};
|
||||
// Call begin in locked context to allow data race free iteration
|
||||
// even if there is parallel inserts to the begin after unlocking.
|
||||
auto iter = callbacks_.begin();
|
||||
lock.unlock();
|
||||
for (; iter != callbacks_.end(); ++iter) {
|
||||
// Quickly skip blocked calls without memory writes
|
||||
if (!*iter)
|
||||
continue;
|
||||
// Protect connection from deletion while we are about to call
|
||||
// it.
|
||||
auto cb = iter->prepareCall();
|
||||
if (cb)
|
||||
combiner(*cb, std::forward<Args>(arg)...);
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(Args&&... arg)
|
||||
{
|
||||
auto combiner = [](const Callback& cb, Args&&... arg2)
|
||||
{
|
||||
cb(std::forward<Args>(arg2)...);
|
||||
};
|
||||
(*this)(combiner,std::forward<Args>(arg)...);
|
||||
}
|
||||
|
||||
~SignalImpl() {
|
||||
// Check that callbacks are empty. If this triggers then signal may
|
||||
// have to be extended to allow automatic disconnection of active
|
||||
// connections in the destructor.
|
||||
if (std::is_same<tag, signal_inline_tag>::value)
|
||||
assert(callbacks_.empty() && "It is very likely that this signal should use signal_shared_tag");
|
||||
}
|
||||
|
||||
//! Simplify access to pimpl when it is inline
|
||||
SignalImpl* operator->() {
|
||||
return this;
|
||||
}
|
||||
SignalImpl& operator*() {
|
||||
return *this;
|
||||
}
|
||||
|
||||
SignalImpl() = default;
|
||||
private:
|
||||
SignalImpl(const SignalImpl&) :
|
||||
SignalImpl{}
|
||||
{}
|
||||
std::mutex access_;
|
||||
CallbackContainer callbacks_;
|
||||
int recursion_;
|
||||
bool deleted_;
|
||||
friend Signal<RT(Args...), tag>;
|
||||
};
|
||||
|
||||
template<typename RT, typename... Args>
|
||||
struct selectImpl<RT(Args...), signal_inline_tag> {
|
||||
using impl_t = SignalImpl<RT(Args...), signal_inline_tag>;
|
||||
using interface_t = Signal<RT(Args...), signal_inline_tag>;
|
||||
using type = impl_t;
|
||||
using weak_ptr = impl_t*;
|
||||
struct ptr_from_this {
|
||||
weak_ptr shared_from_this()
|
||||
{
|
||||
return static_cast<weak_ptr>(this);
|
||||
}
|
||||
};
|
||||
using parent_t = ptr_from_this;
|
||||
|
||||
selectImpl() = default;
|
||||
|
||||
// Disallow copies for inline version.
|
||||
selectImpl(const selectImpl&) = delete;
|
||||
selectImpl(selectImpl&&) = delete;
|
||||
selectImpl& operator=(const selectImpl&) = delete;
|
||||
selectImpl& operator=(selectImpl&&) = delete;
|
||||
|
||||
|
||||
static type make() {
|
||||
return {};
|
||||
}
|
||||
|
||||
static void reset(weak_ptr& ptr) {
|
||||
ptr = nullptr;
|
||||
}
|
||||
|
||||
static weak_ptr lock(weak_ptr& ptr) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static weak_ptr get(interface_t& signal) {
|
||||
return &signal.pimpl;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename RT, typename... Args>
|
||||
struct selectImpl<RT(Args...), signal_shared_tag> {
|
||||
using impl_t = SignalImpl<RT(Args...), signal_shared_tag>;
|
||||
using interface_t = Signal<RT(Args...), signal_shared_tag>;
|
||||
using type = std::shared_ptr<impl_t>;
|
||||
using weak_ptr = std::weak_ptr<impl_t>;
|
||||
using parent_t = std::enable_shared_from_this<impl_t>;
|
||||
|
||||
// Allow copies for shared version
|
||||
|
||||
static type make() {
|
||||
return std::make_shared<SignalImpl<RT(Args...), signal_shared_tag>>();
|
||||
}
|
||||
|
||||
static void reset(weak_ptr& ptr) {
|
||||
ptr.reset();
|
||||
}
|
||||
|
||||
static type lock(weak_ptr& ptr) {
|
||||
return ptr.lock();
|
||||
}
|
||||
|
||||
static weak_ptr get(interface_t& signal) {
|
||||
return signal.pimpl;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*!
|
||||
* As I couldn't figure out which signal library would be a good. Too bad all
|
||||
* signal libraries seem to be either heavy with unnecessary features or written
|
||||
* before C++11/14 have become useable targets. That seems to indicate everyone
|
||||
* is now building signal system with standard components.
|
||||
*
|
||||
* Implementation and interface is build around std::function holding delegates
|
||||
* to a function pointer or a functor. One can put there example lambda function
|
||||
* that captures this pointer from connect side. The lambda function then calls
|
||||
* the slot method of object correctly.
|
||||
*
|
||||
* It is fairly simple to change the signal signature to directly call methods
|
||||
* but internally that std::function becomes more complex. The pointer to
|
||||
* member function is problematic because multiple inheritance requires
|
||||
* adjustments to this. The lambda capture approach should be easy to use while
|
||||
* letting compiler optimize method call in the callee side.
|
||||
*
|
||||
* DFHack::Signal::Connection is an connection handle. The handle can be used to
|
||||
* disconnect and block a callback. Connection destructor will automatically
|
||||
* disconnect from the signal.
|
||||
*
|
||||
* DFHack::Signal::BlockGuard is an automatic blocked callback guard object. It
|
||||
* prevents any signals from calling the slot as long the BlockGuard object is
|
||||
* alive. Internally it replaces the callback with an empty callback and stores
|
||||
* the real callback in a member variable. Destructor then puts back the real
|
||||
* callback. This allows easily recursive BlockGuard work correctly because only
|
||||
* the first BlockGuard has the real callback.
|
||||
*
|
||||
* signal_inline_tag requires careful destruction order where all connection are
|
||||
* disconnected before signal destruction. The implementation is specifically
|
||||
* targeting places like static and singleton variables and widget hierarchies.
|
||||
* It provides data race free connect, disconnect and emit operations.
|
||||
*
|
||||
* signal_shared_tag allows a bit more freedom when destroying the Signal. It
|
||||
* adds data race safety between Connection, BlockGuard and destructor. If
|
||||
* multiple callers need access to Signal with potential of destruction of
|
||||
* original owner then callers can use Signal copy constructor to take a strong
|
||||
* reference managed by shared_ptr or weak_ptr with Signal::weak_from_this().
|
||||
* weak_from_this returns an object that forwards call directly to
|
||||
* implementation when the shared_ptr is created using Signal::lock
|
||||
*
|
||||
* \param RT return type is derived from a single signature template argument
|
||||
* \param Args Variable argument type list that is derived from a signature
|
||||
* template argument.
|
||||
* \param tag The tag type which selects between shared_ptr managed pimpl and
|
||||
* inline member variables.
|
||||
*/
|
||||
template<typename RT, typename... Args, typename tag>
|
||||
class Signal<RT(Args...), tag> : protected details::selectImpl<RT(Args...), tag> {
|
||||
public:
|
||||
//! Type of callable that can be connected to the signal.
|
||||
using Callback = std::function<RT(Args...)>;
|
||||
|
||||
protected:
|
||||
using select_t = details::selectImpl<RT(Args...), tag>;
|
||||
using CallbackContainer = typename select_t::impl_t::CallbackContainer;
|
||||
public:
|
||||
using weak_ptr = typename select_t::weak_ptr;
|
||||
|
||||
/*!
|
||||
* Simple connection class that is required to disconnect from the
|
||||
* signal.
|
||||
* \sa SignalImpl::Connection
|
||||
*/
|
||||
using Connection = typename select_t::impl_t::Connection;
|
||||
/*!
|
||||
* BlockGuard allows temporary RAII guard managed blocking of a
|
||||
* connection object.
|
||||
* \sa SignalImpl::BlockGuard
|
||||
*/
|
||||
using BlockGuard = typename select_t::impl_t::BlockGuard;
|
||||
|
||||
/*!
|
||||
* Connect a callback function to the signal
|
||||
*
|
||||
* Safe to call from any context as long as SignalImpl destructor can't be
|
||||
* called simultaneously from other thread.
|
||||
*
|
||||
* \param f callable that will connected to the signal
|
||||
* \return connection handle that can be used to disconnect it
|
||||
*/
|
||||
Connection connect(const Callback& f)
|
||||
{
|
||||
return pimpl->connect(f);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Thread safe connect variant connection and Connected object destruction
|
||||
* can't race with emit from different threads.
|
||||
*
|
||||
* Safe to call from any context as long as SignalImpl destructor can't be
|
||||
* called simultaneously from other thread.
|
||||
*/
|
||||
Connection connect(std::shared_ptr<ConnectedBase> c, const Callback& f)
|
||||
{
|
||||
static_assert(std::is_same<tag, signal_shared_tag>::value,
|
||||
"Race free destruction is only possible with signal_shared_tag");
|
||||
return pimpl->connect(c, f);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Disconnection a callback from slots
|
||||
*
|
||||
* signal_inline_tag:
|
||||
* This may not be called if the callback has been called in same
|
||||
* thread. If callback should trigger destruction an object then
|
||||
* deletion must use deferred. This rule prevents issues if other thread
|
||||
* are trying to call the callback when disconnecting.
|
||||
*
|
||||
* signal_shared_tag:
|
||||
* disconnect can be freely called from anywhere as long as caller holds a
|
||||
* strong reference to the Signal. Strong reference can be obtained by using
|
||||
* Connection::disconnect, Signal copy constructor to have a copy of signal
|
||||
* or weak_ptr from weak_from_this() passed to Signal::lock().
|
||||
*
|
||||
* \param connection the object returned from DFHack::Signal::connect
|
||||
*/
|
||||
void disconnect(Connection& connection)
|
||||
{
|
||||
pimpl->disconnect(connection);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Call all connected callbacks using passed arguments.
|
||||
*
|
||||
* signal_inline_tag:
|
||||
* Must not call operator() from callbacks.
|
||||
* Must not disconnect called callback from inside callback. Solution often
|
||||
* is to set just atomic state variables in callback and do actual
|
||||
* processing including deletion in update handler or logic vmethod.
|
||||
*
|
||||
* signal_shared_tag:
|
||||
* Safe to call from any context as long as SignalImpl destructor can't be
|
||||
* called simultaneously from other thread.
|
||||
* Safe to disconnect any connection from callbacks.
|
||||
*
|
||||
* \param combiner that calls callbacks and processes return values
|
||||
* \param arg arguments list defined by template parameter signature.
|
||||
*/
|
||||
template<typename Combiner>
|
||||
void operator()(Combiner &combiner, Args&&... arg)
|
||||
{
|
||||
(*pimpl)(combiner, std::forward<Args>(arg)...);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Call all connected callbacks using passed arguments.
|
||||
*
|
||||
* signal_inline_tag:
|
||||
* Must not call operator() from callbacks.
|
||||
* Must not disconnect called callback from inside callback. Solution often
|
||||
* is to set just atomic state variables in callback and do actual
|
||||
* processing including deletion in update handler or logic vmethod.
|
||||
*
|
||||
* signal_shared_tag:
|
||||
* Safe to call from any context as long as SignalImpl destructor can't be
|
||||
* called simultaneously from other thread.
|
||||
* Safe to disconnect any connection from callbacks.
|
||||
*
|
||||
* \param arg arguments list defined by template parameter signature.
|
||||
*/
|
||||
void operator()(Args&&... arg)
|
||||
{
|
||||
(*pimpl)(std::forward<Args>(arg)...);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Helper to lock the weak_ptr
|
||||
*/
|
||||
static typename select_t::type lock(weak_ptr& ptr)
|
||||
{
|
||||
return select_t::lock(ptr);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Helper to create a weak reference to pimpl which can be used to access
|
||||
* pimpl directly. If the tag is signal_shared_tag then it provides race
|
||||
* free access to Signal when using Signal::lock and checking returned
|
||||
* shared_ptr.
|
||||
*/
|
||||
weak_ptr weak_from_this() noexcept
|
||||
{
|
||||
return select_t::get(*this);
|
||||
}
|
||||
|
||||
Signal() :
|
||||
pimpl{select_t::make()}
|
||||
{}
|
||||
private:
|
||||
typename select_t::type pimpl;
|
||||
friend select_t;
|
||||
};
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit de83a453d7e55aa48ffc92c8f4c147b1a1acd525
|
||||
Subproject commit 2be1fc4afea4d3345b9b76d0f27f56087ac9b6e0
|
@ -1 +1 @@
|
||||
Subproject commit 4a1953f27c6acd1ceeb2d5447613c106e708c26c
|
||||
Subproject commit 6b4df3995f53e0579fc590a78d68e54fc2ca2b81
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@ -1 +1 @@
|
||||
Subproject commit 87cc0fcad57a3a8ff7b558be9f3523101ac2f69d
|
||||
Subproject commit c8394fa75ff20abcdcdbe975dbf157d21882172e
|
@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
cd "$(dirname "$0")"
|
||||
cd ..
|
||||
grep DF_VERSION CMakeLists.txt | perl -ne 'print "$&\n" if /[\d\.]+/'
|
||||
grep -i 'set(DF_VERSION' CMakeLists.txt | perl -ne 'print "$&\n" if /[\d\.]+/'
|
||||
|