mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-06 08:46:45 +08:00
Compare commits
36 Commits
2815fe0163
...
5d56ef759f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d56ef759f | ||
|
|
eede00165e | ||
|
|
bb3c6eb534 | ||
|
|
70121091d6 | ||
|
|
32c2be29a9 | ||
|
|
2d38a6a6e7 | ||
|
|
831225f763 | ||
|
|
db109165f3 | ||
|
|
180920968f | ||
|
|
ea0a3a4bf6 | ||
|
|
89e9f87f36 | ||
|
|
edc1e80585 | ||
|
|
3571fa58c8 | ||
|
|
99272a452a | ||
|
|
86d9d868c8 | ||
|
|
00162b96b9 | ||
|
|
87edc6fab1 | ||
|
|
70a1f68f01 | ||
|
|
b8d01ddf68 | ||
|
|
40eaab7310 | ||
|
|
8c34d5d4bc | ||
|
|
48d4d6111d | ||
|
|
033f22ae8f | ||
|
|
85bf8263fb | ||
|
|
6db5845a45 | ||
|
|
6c068f7ba4 | ||
|
|
d2ba9dce52 | ||
|
|
72ec8ee42f | ||
|
|
e30ab84ccc | ||
|
|
e333bc754e | ||
|
|
a2d82ec6f0 | ||
|
|
54f05e9536 | ||
|
|
e5546179e2 | ||
|
|
e5a56f02f1 | ||
|
|
ed8f9d06a6 | ||
|
|
67776aea65 |
2
.github/workflows/c-cpp.yml
vendored
2
.github/workflows/c-cpp.yml
vendored
@ -2,7 +2,7 @@ name: C/C++ CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, develop, issue-* ]
|
||||
branches: [ master, develop, issue-*, feature/* ]
|
||||
pull_request:
|
||||
branches: [ master, develop ]
|
||||
|
||||
|
||||
@ -5,12 +5,12 @@
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
|
||||
#include "libipc/export.h"
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/def.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
class IPC_EXPORT buffer {
|
||||
class LIBIPC_EXPORT buffer {
|
||||
public:
|
||||
using destructor_t = void (*)(void*, std::size_t);
|
||||
|
||||
@ -57,8 +57,8 @@ public:
|
||||
};
|
||||
}
|
||||
|
||||
friend IPC_EXPORT bool operator==(buffer const & b1, buffer const & b2);
|
||||
friend IPC_EXPORT bool operator!=(buffer const & b1, buffer const & b2);
|
||||
friend LIBIPC_EXPORT bool operator==(buffer const & b1, buffer const & b2);
|
||||
friend LIBIPC_EXPORT bool operator!=(buffer const & b1, buffer const & b2);
|
||||
|
||||
private:
|
||||
class buffer_;
|
||||
|
||||
66
include/libipc/concur/intrusive_stack.h
Normal file
66
include/libipc/concur/intrusive_stack.h
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* \file libconcur/intrusive_stack.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Define concurrent intrusive stack.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace ipc {
|
||||
namespace concur {
|
||||
|
||||
/// \brief Intrusive stack node.
|
||||
/// \tparam T The type of the value.
|
||||
template <typename T>
|
||||
struct intrusive_node {
|
||||
T value;
|
||||
std::atomic<intrusive_node *> next;
|
||||
};
|
||||
|
||||
/// \brief Intrusive stack.
|
||||
/// \tparam T The type of the value.
|
||||
/// \tparam Node The type of the node.
|
||||
template <typename T, typename Node = intrusive_node<T>>
|
||||
class intrusive_stack {
|
||||
public:
|
||||
using node = Node;
|
||||
|
||||
private:
|
||||
std::atomic<node *> top_{nullptr};
|
||||
|
||||
public:
|
||||
intrusive_stack(intrusive_stack const &) = delete;
|
||||
intrusive_stack(intrusive_stack &&) = delete;
|
||||
intrusive_stack &operator=(intrusive_stack const &) = delete;
|
||||
intrusive_stack &operator=(intrusive_stack &&) = delete;
|
||||
|
||||
constexpr intrusive_stack() noexcept = default;
|
||||
|
||||
bool empty() const noexcept {
|
||||
return top_.load(std::memory_order_acquire) == nullptr;
|
||||
}
|
||||
|
||||
void push(node *n) noexcept {
|
||||
node *old_top = top_.load(std::memory_order_acquire);
|
||||
do {
|
||||
n->next.store(old_top, std::memory_order_relaxed);
|
||||
} while (!top_.compare_exchange_weak(old_top, n, std::memory_order_release
|
||||
, std::memory_order_acquire));
|
||||
}
|
||||
|
||||
node *pop() noexcept {
|
||||
node *old_top = top_.load(std::memory_order_acquire);
|
||||
do {
|
||||
if (old_top == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
} while (!top_.compare_exchange_weak(old_top, old_top->next.load(std::memory_order_relaxed)
|
||||
, std::memory_order_release
|
||||
, std::memory_order_acquire));
|
||||
return old_top;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace concur
|
||||
} // namespace ipc
|
||||
@ -2,14 +2,14 @@
|
||||
|
||||
#include <cstdint> // std::uint64_t
|
||||
|
||||
#include "libipc/export.h"
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/mutex.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace sync {
|
||||
|
||||
class IPC_EXPORT condition {
|
||||
class LIBIPC_EXPORT condition {
|
||||
condition(condition const &) = delete;
|
||||
condition &operator=(condition const &) = delete;
|
||||
|
||||
|
||||
@ -26,25 +26,26 @@ using uint_t = typename uint<N>::type;
|
||||
// constants
|
||||
|
||||
enum : std::uint32_t {
|
||||
invalid_value = (std::numeric_limits<std::uint32_t>::max)(),
|
||||
default_timeout = 100, // ms
|
||||
invalid_value = (std::numeric_limits<std::uint32_t>::max)(),
|
||||
default_timeout = 100, // ms
|
||||
};
|
||||
|
||||
enum : std::size_t {
|
||||
data_length = 64,
|
||||
large_msg_limit = data_length,
|
||||
large_msg_align = 1024,
|
||||
large_msg_cache = 32,
|
||||
central_cache_default_size = 1024 * 1024, ///< 1MB
|
||||
data_length = 64,
|
||||
large_msg_limit = data_length,
|
||||
large_msg_align = 1024,
|
||||
large_msg_cache = 32,
|
||||
};
|
||||
|
||||
enum class relat { // multiplicity of the relationship
|
||||
single,
|
||||
multi
|
||||
single,
|
||||
multi
|
||||
};
|
||||
|
||||
enum class trans { // transmission
|
||||
unicast,
|
||||
broadcast
|
||||
unicast,
|
||||
broadcast
|
||||
};
|
||||
|
||||
// producer-consumer policy flag
|
||||
@ -57,9 +58,9 @@ struct relat_trait;
|
||||
|
||||
template <relat Rp, relat Rc, trans Ts>
|
||||
struct relat_trait<wr<Rp, Rc, Ts>> {
|
||||
constexpr static bool is_multi_producer = (Rp == relat::multi);
|
||||
constexpr static bool is_multi_consumer = (Rc == relat::multi);
|
||||
constexpr static bool is_broadcast = (Ts == trans::broadcast);
|
||||
constexpr static bool is_multi_producer = (Rp == relat::multi);
|
||||
constexpr static bool is_multi_consumer = (Rc == relat::multi);
|
||||
constexpr static bool is_broadcast = (Ts == trans::broadcast);
|
||||
};
|
||||
|
||||
template <template <typename> class Policy, typename Flag>
|
||||
@ -67,7 +68,7 @@ struct relat_trait<Policy<Flag>> : relat_trait<Flag> {};
|
||||
|
||||
// the prefix tag of a channel
|
||||
struct prefix {
|
||||
char const *str;
|
||||
char const *str;
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
# define IPC_DECL_EXPORT Q_DECL_EXPORT
|
||||
# define IPC_DECL_IMPORT Q_DECL_IMPORT
|
||||
|
||||
#else // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
/*
|
||||
* Compiler & system detection for IPC_DECL_EXPORT & IPC_DECL_IMPORT.
|
||||
* Not using QtCore cause it shouldn't depend on Qt.
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define IPC_DECL_EXPORT __declspec(dllexport)
|
||||
# define IPC_DECL_IMPORT __declspec(dllimport)
|
||||
#elif defined(__ARMCC__) || defined(__CC_ARM)
|
||||
# if defined(ANDROID) || defined(__linux__) || defined(__linux)
|
||||
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
|
||||
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
|
||||
# else
|
||||
# define IPC_DECL_EXPORT __declspec(dllexport)
|
||||
# define IPC_DECL_IMPORT __declspec(dllimport)
|
||||
# endif
|
||||
#elif defined(__GNUC__)
|
||||
# if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
|
||||
defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
|
||||
# define IPC_DECL_EXPORT __declspec(dllexport)
|
||||
# define IPC_DECL_IMPORT __declspec(dllimport)
|
||||
# else
|
||||
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
|
||||
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
|
||||
# endif
|
||||
#else
|
||||
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
|
||||
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#endif // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
/*
|
||||
* Define IPC_EXPORT for exporting function & class.
|
||||
*/
|
||||
|
||||
#ifndef IPC_EXPORT
|
||||
#if defined(LIBIPC_LIBRARY_SHARED_BUILDING__)
|
||||
# define IPC_EXPORT IPC_DECL_EXPORT
|
||||
#elif defined(LIBIPC_LIBRARY_SHARED_USING__)
|
||||
# define IPC_EXPORT IPC_DECL_IMPORT
|
||||
#else
|
||||
# define IPC_EXPORT
|
||||
#endif
|
||||
#endif /*IPC_EXPORT*/
|
||||
74
include/libipc/imp/aligned.h
Normal file
74
include/libipc/imp/aligned.h
Normal file
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* \file libipc/aligned.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Defines the type suitable for use as uninitialized storage for types of given type.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
#include "libipc/imp/byte.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \brief The type suitable for use as uninitialized storage for types of given type.
|
||||
* std::aligned_storage is deprecated in C++23, so we define our own.
|
||||
* \tparam T The type to be aligned.
|
||||
* \tparam AlignT The alignment of the type.
|
||||
* \see https://en.cppreference.com/w/cpp/types/aligned_storage
|
||||
* https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1413r3.pdf
|
||||
*/
|
||||
template <typename T, std::size_t AlignT = alignof(T)>
|
||||
class aligned {
|
||||
alignas(AlignT) std::array<ipc::byte, sizeof(T)> storage_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* \brief Returns a pointer to the aligned storage.
|
||||
* \return A pointer to the aligned storage.
|
||||
*/
|
||||
T *ptr() noexcept {
|
||||
return reinterpret_cast<T *>(storage_.data());
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns a pointer to the aligned storage.
|
||||
* \return A pointer to the aligned storage.
|
||||
*/
|
||||
T const *ptr() const noexcept {
|
||||
return reinterpret_cast<const T *>(storage_.data());
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns a reference to the aligned storage.
|
||||
* \return A reference to the aligned storage.
|
||||
*/
|
||||
T &ref() noexcept {
|
||||
return *ptr();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns a reference to the aligned storage.
|
||||
* \return A reference to the aligned storage.
|
||||
*/
|
||||
T const &ref() const noexcept {
|
||||
return *ptr();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Rounds up the given value to the given alignment.
|
||||
* \tparam T The type of the value.
|
||||
* \param value The value to be rounded up.
|
||||
* \param alignment The alignment to be rounded up to.
|
||||
* \return The rounded up value.
|
||||
* \see https://stackoverflow.com/questions/3407012/c-rounding-up-to-the-nearest-multiple-of-a-number
|
||||
*/
|
||||
template <typename T>
|
||||
constexpr T round_up(T value, T alignment) noexcept {
|
||||
return (value + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
178
include/libipc/imp/byte.h
Normal file
178
include/libipc/imp/byte.h
Normal file
@ -0,0 +1,178 @@
|
||||
/**
|
||||
* \file libipc/byte.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Define the byte type.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <cstdint>
|
||||
#include <cstddef> // std::byte (since C++17)
|
||||
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/imp/span.h"
|
||||
#include "libipc/imp/fmt.h"
|
||||
|
||||
#if defined(LIBIPC_CPP_17) && defined(__cpp_lib_byte)
|
||||
#define LIBIPC_CPP_LIB_BYTE_
|
||||
#endif // __cpp_lib_byte
|
||||
|
||||
namespace ipc {
|
||||
|
||||
class byte;
|
||||
|
||||
namespace detail_byte {
|
||||
|
||||
template <typename T>
|
||||
using is_integral =
|
||||
typename std::enable_if<std::is_integral<T>::value>::type;
|
||||
|
||||
template <typename T>
|
||||
using is_not_byte =
|
||||
typename std::enable_if<!std::is_same<
|
||||
typename std::remove_cv<T>::type, byte>::value>::type;
|
||||
|
||||
} // namespace detail_byte
|
||||
|
||||
/**
|
||||
* \brief A distinct type that implements the concept of byte as specified in the C++ language definition.
|
||||
* \see https://en.cppreference.com/w/cpp/types/byte
|
||||
*/
|
||||
class byte {
|
||||
std::uint8_t bits_;
|
||||
|
||||
public:
|
||||
byte() noexcept = default;
|
||||
|
||||
template <typename T, typename = detail_byte::is_integral<T>>
|
||||
constexpr byte(T v) noexcept
|
||||
: bits_(static_cast<std::uint8_t>(v)) {}
|
||||
|
||||
#ifdef LIBIPC_CPP_LIB_BYTE_
|
||||
constexpr byte(std::byte b) noexcept
|
||||
: byte(std::to_integer<std::uint8_t>(b)) {}
|
||||
#endif // LIBIPC_CPP_LIB_BYTE_
|
||||
|
||||
template <typename T, typename = detail_byte::is_integral<T>>
|
||||
constexpr operator T() const noexcept {
|
||||
return static_cast<T>(bits_);
|
||||
}
|
||||
|
||||
#ifdef LIBIPC_CPP_LIB_BYTE_
|
||||
constexpr operator std::byte() const noexcept {
|
||||
/// \brief C++17 relaxed enum class initialization rules.
|
||||
/// \see https://en.cppreference.com/w/cpp/language/enum#enum_relaxed_init_cpp17
|
||||
return std::byte{bits_};
|
||||
}
|
||||
#endif // LIBIPC_CPP_LIB_BYTE_
|
||||
|
||||
friend bool operator==(byte const &lhs, byte const &rhs) noexcept {
|
||||
return lhs.bits_ == rhs.bits_;
|
||||
}
|
||||
|
||||
friend bool operator!=(byte const &lhs, byte const &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Non-member functions.
|
||||
*/
|
||||
|
||||
template <typename T, typename = detail_byte::is_integral<T>>
|
||||
constexpr T to_integer(byte b) noexcept {
|
||||
return T(b);
|
||||
}
|
||||
|
||||
/// \brief std::operator<<, operator>>
|
||||
|
||||
template <typename T, typename = detail_byte::is_integral<T>>
|
||||
constexpr byte operator<<(byte b, T shift) noexcept {
|
||||
return byte(to_integer<unsigned>(b) << shift);
|
||||
}
|
||||
|
||||
template <typename T, typename = detail_byte::is_integral<T>>
|
||||
constexpr byte operator>>(byte b, T shift) noexcept {
|
||||
return byte(to_integer<unsigned>(b) >> shift);
|
||||
}
|
||||
|
||||
/// \brief std::operator<<=, operator>>=
|
||||
|
||||
template <typename T, typename = detail_byte::is_integral<T>>
|
||||
constexpr byte &operator<<=(byte &b, T shift) noexcept {
|
||||
return b = b << shift;
|
||||
}
|
||||
|
||||
template <typename T, typename = detail_byte::is_integral<T>>
|
||||
constexpr byte &operator>>=(byte &b, T shift) noexcept {
|
||||
return b = b >> shift;
|
||||
}
|
||||
|
||||
/// \brief std::operator|, operator&, operator^, operator~
|
||||
|
||||
constexpr byte operator|(byte l, byte r) noexcept { return byte(to_integer<unsigned>(l) | to_integer<unsigned>(r)); }
|
||||
constexpr byte operator&(byte l, byte r) noexcept { return byte(to_integer<unsigned>(l) & to_integer<unsigned>(r)); }
|
||||
constexpr byte operator^(byte l, byte r) noexcept { return byte(to_integer<unsigned>(l) ^ to_integer<unsigned>(r)); }
|
||||
constexpr byte operator~(byte b) noexcept { return byte(~to_integer<unsigned>(b)); }
|
||||
|
||||
/// \brief std::operator|=, operator&=, operator^=
|
||||
|
||||
constexpr byte &operator|=(byte &l, byte r) noexcept { return l = l | r; }
|
||||
constexpr byte &operator&=(byte &l, byte r) noexcept { return l = l & r; }
|
||||
constexpr byte &operator^=(byte &l, byte r) noexcept { return l = l ^ r; }
|
||||
|
||||
/// \brief Cast pointer to byte*.
|
||||
|
||||
template <typename T, typename = detail_byte::is_not_byte<T>>
|
||||
byte *byte_cast(T *p) noexcept {
|
||||
return reinterpret_cast<byte *>(p);
|
||||
}
|
||||
|
||||
template <typename T, typename = detail_byte::is_not_byte<T>>
|
||||
byte const *byte_cast(T const *p) noexcept {
|
||||
return reinterpret_cast<byte const *>(p);
|
||||
}
|
||||
|
||||
/// \brief Cast byte* to a pointer of another type.
|
||||
|
||||
template <typename T, typename = detail_byte::is_not_byte<T>>
|
||||
T *byte_cast(byte *p) noexcept {
|
||||
if (reinterpret_cast<std::size_t>(p) % alignof(T) != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
return reinterpret_cast<T *>(p);
|
||||
}
|
||||
|
||||
template <typename T, typename U = typename std::add_const<T>::type,
|
||||
typename = detail_byte::is_not_byte<T>>
|
||||
U *byte_cast(byte const *p) noexcept {
|
||||
if (reinterpret_cast<std::size_t>(p) % alignof(T) != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
return reinterpret_cast<U *>(p);
|
||||
}
|
||||
|
||||
/// \brief Converts a span into a view of its underlying bytes.
|
||||
/// \see https://en.cppreference.com/w/cpp/container/span/as_bytes
|
||||
|
||||
template <typename T,
|
||||
typename Byte = typename std::conditional<std::is_const<T>::value, byte const, byte>::type>
|
||||
auto as_bytes(span<T> s) noexcept -> span<Byte> {
|
||||
return {byte_cast(s.data()), s.size_bytes()};
|
||||
}
|
||||
|
||||
/// \brief Custom defined fmt_to method for imp::fmt
|
||||
namespace detail_tag_invoke {
|
||||
|
||||
inline bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, ipc::byte b) {
|
||||
return ipc::to_string(ctx, static_cast<std::uint8_t>(b), "02x");
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
typename = std::enable_if_t<std::is_same<std::decay_t<T>, ipc::byte>::value>>
|
||||
bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, fmt_ref<T> arg) noexcept {
|
||||
return ipc::to_string(ctx, static_cast<std::uint8_t>(arg.param), arg.fstr);
|
||||
}
|
||||
|
||||
} // namespace detail_tag_invoke
|
||||
} // namespace ipc
|
||||
36
include/libipc/imp/codecvt.h
Normal file
36
include/libipc/imp/codecvt.h
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* \file libipc/codecvt.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Character set conversion interface.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstddef>
|
||||
|
||||
#include "libipc/imp/export.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \brief The transform between UTF-8/16/32
|
||||
*
|
||||
* \param des The target string pointer can be nullptr
|
||||
* \param dlen The target string length can be 0
|
||||
*/
|
||||
template <typename CharT, typename CharU>
|
||||
LIBIPC_EXPORT std::size_t cvt_cstr(CharT const *src, std::size_t slen, CharU *des, std::size_t dlen) noexcept;
|
||||
|
||||
template <typename CharT, typename TraitsT, typename AllocT,
|
||||
typename CharU, typename TraitsU, typename AllocU>
|
||||
void cvt_sstr(std::basic_string<CharT, TraitsT, AllocT> const &src, std::basic_string<CharU, TraitsU, AllocU> &des) {
|
||||
std::size_t dlen = cvt_cstr(src.c_str(), src.size(), (CharU *)nullptr, 0);
|
||||
if (dlen == 0) {
|
||||
des.clear();
|
||||
return;
|
||||
}
|
||||
des.resize(dlen);
|
||||
cvt_cstr(src.c_str(), src.size(), &des[0], des.size());
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
226
include/libipc/imp/detect_plat.h
Normal file
226
include/libipc/imp/detect_plat.h
Normal file
@ -0,0 +1,226 @@
|
||||
/**
|
||||
* \file libipc/detect_plat.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Define platform detection related interfaces.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/// \brief OS check.
|
||||
|
||||
#if defined(WINCE) || defined(_WIN32_WCE)
|
||||
# define LIBIPC_OS_WINCE
|
||||
#elif defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
|
||||
(defined(__x86_64) && defined(__MSYS__))
|
||||
#define LIBIPC_OS_WIN64
|
||||
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \
|
||||
defined(__NT__) || defined(__MSYS__)
|
||||
# define LIBIPC_OS_WIN32
|
||||
#elif defined(__QNX__) || defined(__QNXNTO__)
|
||||
# define LIBIPC_OS_QNX
|
||||
#elif defined(__APPLE__)
|
||||
# define LIBIPC_OS_APPLE
|
||||
#elif defined(ANDROID) || defined(__ANDROID__)
|
||||
# define LIBIPC_OS_ANDROID
|
||||
#elif defined(__linux__) || defined(__linux)
|
||||
# define LIBIPC_OS_LINUX
|
||||
#elif defined(_POSIX_VERSION)
|
||||
# define LIBIPC_OS_POSIX
|
||||
#else
|
||||
# error "This OS is unsupported."
|
||||
#endif
|
||||
|
||||
#if defined(LIBIPC_OS_WIN32) || defined(LIBIPC_OS_WIN64) || \
|
||||
defined(LIBIPC_OS_WINCE)
|
||||
# define LIBIPC_OS_WIN
|
||||
#endif
|
||||
|
||||
/// \brief Compiler check.
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define LIBIPC_CC_MSVC _MSC_VER
|
||||
# define LIBIPC_CC_MSVC_2015 1900
|
||||
# define LIBIPC_CC_MSVC_2017 1910
|
||||
# define LIBIPC_CC_MSVC_2019 1920
|
||||
# define LIBIPC_CC_MSVC_2022 1930
|
||||
#elif defined(__GNUC__)
|
||||
# define LIBIPC_CC_GNUC __GNUC__
|
||||
# if defined(__clang__)
|
||||
# define LIBIPC_CC_CLANG
|
||||
#endif
|
||||
#else
|
||||
# error "This compiler is unsupported."
|
||||
#endif
|
||||
|
||||
/// \brief Instruction set.
|
||||
/// \see https://sourceforge.net/p/predef/wiki/Architectures/
|
||||
|
||||
#if defined(_M_X64) || defined(_M_AMD64) || \
|
||||
defined(__x86_64__) || defined(__x86_64) || \
|
||||
defined(__amd64__) || defined(__amd64)
|
||||
# define LIBIPC_INSTR_X64
|
||||
#elif defined(_M_IA64) || defined(__IA64__) || defined(_IA64) || \
|
||||
defined(__ia64__) || defined(__ia64)
|
||||
# define LIBIPC_INSTR_I64
|
||||
#elif defined(_M_IX86) || defined(_X86_) || defined(__i386__) || defined(__i386)
|
||||
# define LIBIPC_INSTR_X86
|
||||
#elif defined(_M_ARM64) || defined(__arm64__) || defined(__aarch64__)
|
||||
# define LIBIPC_INSTR_ARM64
|
||||
#elif defined(_M_ARM) || defined(_ARM) || defined(__arm__) || defined(__arm)
|
||||
# define LIBIPC_INSTR_ARM32
|
||||
#else
|
||||
# error "This instruction set is unsupported."
|
||||
#endif
|
||||
|
||||
#if defined(LIBIPC_INSTR_X86) || defined(LIBIPC_INSTR_X64)
|
||||
# define LIBIPC_INSTR_X86_64
|
||||
#elif defined(LIBIPC_INSTR_ARM32) || defined(LIBIPC_INSTR_ARM64)
|
||||
# define LIBIPC_INSTR_ARM
|
||||
#endif
|
||||
|
||||
/// \brief Byte order.
|
||||
|
||||
#if defined(__BYTE_ORDER__)
|
||||
# define LIBIPC_ENDIAN_BIG (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
||||
# define LIBIPC_ENDIAN_LIT (!LIBIPC_ENDIAN_BIG)
|
||||
#else
|
||||
# define LIBIPC_ENDIAN_BIG (0)
|
||||
# define LIBIPC_ENDIAN_LIT (1)
|
||||
#endif
|
||||
|
||||
/// \brief C++ version.
|
||||
|
||||
#if (__cplusplus >= 202002L) && !defined(LIBIPC_CPP_20)
|
||||
# define LIBIPC_CPP_20
|
||||
#endif
|
||||
#if (__cplusplus >= 201703L) && !defined(LIBIPC_CPP_17)
|
||||
# define LIBIPC_CPP_17
|
||||
#endif
|
||||
#if /*(__cplusplus >= 201402L) &&*/ !defined(LIBIPC_CPP_14)
|
||||
# define LIBIPC_CPP_14
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_CPP_20) && \
|
||||
!defined(LIBIPC_CPP_17) && \
|
||||
!defined(LIBIPC_CPP_14)
|
||||
# error "This C++ version is unsupported."
|
||||
#endif
|
||||
|
||||
/// \brief Feature cross-platform adaptation.
|
||||
|
||||
#if defined(LIBIPC_CPP_17)
|
||||
# define LIBIPC_INLINE_CONSTEXPR inline constexpr
|
||||
#else
|
||||
# define LIBIPC_INLINE_CONSTEXPR constexpr
|
||||
#endif
|
||||
|
||||
/// \brief C++ attributes.
|
||||
/// \see https://en.cppreference.com/w/cpp/language/attributes
|
||||
|
||||
#if defined(__has_cpp_attribute)
|
||||
# if __has_cpp_attribute(fallthrough)
|
||||
# define LIBIPC_FALLTHROUGH [[fallthrough]]
|
||||
# endif
|
||||
# if __has_cpp_attribute(maybe_unused)
|
||||
# define LIBIPC_UNUSED [[maybe_unused]]
|
||||
# endif
|
||||
# if __has_cpp_attribute(likely)
|
||||
# define LIBIPC_LIKELY(...) (__VA_ARGS__) [[likely]]
|
||||
# endif
|
||||
# if __has_cpp_attribute(unlikely)
|
||||
# define LIBIPC_UNLIKELY(...) (__VA_ARGS__) [[unlikely]]
|
||||
# endif
|
||||
# if __has_cpp_attribute(nodiscard)
|
||||
# define LIBIPC_NODISCARD [[nodiscard]]
|
||||
# endif
|
||||
# if __has_cpp_attribute(assume)
|
||||
# define LIBIPC_ASSUME(...) [[assume(__VA_ARGS__)]]
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_FALLTHROUGH)
|
||||
# if defined(LIBIPC_CC_GNUC)
|
||||
# define LIBIPC_FALLTHROUGH __attribute__((__fallthrough__))
|
||||
# else
|
||||
# define LIBIPC_FALLTHROUGH
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_UNUSED)
|
||||
# if defined(LIBIPC_CC_GNUC)
|
||||
# define LIBIPC_UNUSED __attribute__((__unused__))
|
||||
# elif defined(LIBIPC_CC_MSVC)
|
||||
# define LIBIPC_UNUSED __pragma(warning(suppress: 4100 4101 4189))
|
||||
# else
|
||||
# define LIBIPC_UNUSED
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_LIKELY)
|
||||
# if defined(__has_builtin)
|
||||
# if __has_builtin(__builtin_expect)
|
||||
# define LIBIPC_LIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 1))
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_LIKELY)
|
||||
# define LIBIPC_LIKELY(...) (__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_UNLIKELY)
|
||||
# if defined(__has_builtin)
|
||||
# if __has_builtin(__builtin_expect)
|
||||
# define LIBIPC_UNLIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 0))
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_UNLIKELY)
|
||||
# define LIBIPC_UNLIKELY(...) (__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_NODISCARD)
|
||||
/// \see https://stackoverflow.com/questions/4226308/msvc-equivalent-of-attribute-warn-unused-result
|
||||
# if defined(LIBIPC_CC_GNUC) && (LIBIPC_CC_GNUC >= 4)
|
||||
# define LIBIPC_NODISCARD __attribute__((warn_unused_result))
|
||||
# elif defined(LIBIPC_CC_MSVC) && (LIBIPC_CC_MSVC >= 1700)
|
||||
# define LIBIPC_NODISCARD _Check_return_
|
||||
# else
|
||||
# define LIBIPC_NODISCARD
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_ASSUME)
|
||||
# if defined(__has_builtin)
|
||||
# if __has_builtin(__builtin_assume)
|
||||
/// \see https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-assume
|
||||
# define LIBIPC_ASSUME(...) __builtin_assume(__VA_ARGS__)
|
||||
# elif __has_builtin(__builtin_unreachable)
|
||||
/// \see https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005funreachable
|
||||
# define LIBIPC_ASSUME(...) do { if (!(__VA_ARGS__)) __builtin_unreachable(); } while (false)
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_ASSUME)
|
||||
# if defined(LIBIPC_CC_MSVC)
|
||||
/// \see https://learn.microsoft.com/en-us/cpp/intrinsics/assume?view=msvc-140
|
||||
# define LIBIPC_ASSUME(...) __assume(__VA_ARGS__)
|
||||
# else
|
||||
# define LIBIPC_ASSUME(...)
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/// \see https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_exceptions.html
|
||||
/// https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros
|
||||
/// https://stackoverflow.com/questions/6487013/programmatically-determine-whether-exceptions-are-enabled
|
||||
#if defined(__cpp_exceptions) && __cpp_exceptions || \
|
||||
defined(__EXCEPTIONS) || defined(_CPPUNWIND)
|
||||
# define LIBIPC_TRY try
|
||||
# define LIBIPC_CATCH(...) catch (__VA_ARGS__)
|
||||
# define LIBIPC_THROW($EXCEPTION, ...) throw $EXCEPTION
|
||||
#else
|
||||
# define LIBIPC_TRY if (true)
|
||||
# define LIBIPC_CATCH(...) else if (false)
|
||||
# define LIBIPC_THROW($EXCEPTION, ...) return __VA_ARGS__
|
||||
#endif
|
||||
27
include/libipc/imp/error.h
Normal file
27
include/libipc/imp/error.h
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* \file libipc/error.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief A platform-dependent error code.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <system_error>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/imp/fmt_cpo.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \brief Custom defined fmt_to method for imp::fmt
|
||||
*/
|
||||
namespace detail_tag_invoke {
|
||||
|
||||
inline bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, std::error_code const &ec) noexcept {
|
||||
return fmt_to(ctx, '[', ec.value(), ": ", ec.message(), ']');
|
||||
}
|
||||
|
||||
} // namespace detail_tag_invoke
|
||||
} // namespace ipc
|
||||
392
include/libipc/imp/expected.h
Normal file
392
include/libipc/imp/expected.h
Normal file
@ -0,0 +1,392 @@
|
||||
/**
|
||||
* \file libipc/expected.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Provides a way to store either of two values.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility> // std::exchange
|
||||
#include <array>
|
||||
#include <memory> // std::addressof
|
||||
#include <cstddef> // std::nullptr_t
|
||||
|
||||
#include "libipc/imp/uninitialized.h"
|
||||
#include "libipc/imp/generic.h"
|
||||
#include "libipc/imp/byte.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \brief In-place construction tag for unexpected value in expected.
|
||||
* \see https://en.cppreference.com/w/cpp/utility/expected/unexpect_t
|
||||
*/
|
||||
struct unexpected_t {
|
||||
explicit unexpected_t() = default;
|
||||
};
|
||||
constexpr unexpected_t unexpected{};
|
||||
|
||||
/**
|
||||
* \class template <typename T, typename E> expected
|
||||
* \brief Provides a way to store either of two values.
|
||||
* \see https://en.cppreference.com/w/cpp/utility/expected
|
||||
* \tparam T - the type of the expected value.
|
||||
* \tparam E - the type of the unexpected value.
|
||||
*/
|
||||
template <typename T, typename E>
|
||||
class expected;
|
||||
|
||||
namespace detail_expected {
|
||||
|
||||
template <typename T, typename E>
|
||||
struct data_union {
|
||||
using const_value_t = typename std::add_const<T>::type;
|
||||
using const_error_t = typename std::add_const<E>::type;
|
||||
|
||||
union {
|
||||
T value_; ///< the expected value
|
||||
E error_; ///< the unexpected value
|
||||
};
|
||||
|
||||
data_union(data_union const &) = delete;
|
||||
data_union &operator=(data_union const &) = delete;
|
||||
|
||||
data_union(std::nullptr_t) noexcept {}
|
||||
~data_union() {}
|
||||
|
||||
template <typename... A>
|
||||
data_union(in_place_t, A &&...args) : value_{std::forward<A>(args)...} {}
|
||||
template <typename... A>
|
||||
data_union(unexpected_t, A &&...args) : error_{std::forward<A>(args)...} {}
|
||||
|
||||
void destruct_value() noexcept { destroy(&value_); }
|
||||
void destruct_error() noexcept { destroy(&error_); }
|
||||
|
||||
const_value_t & value() const & noexcept { return value_; }
|
||||
T & value() & noexcept { return value_; }
|
||||
const_value_t &&value() const && noexcept { return std::move(value_); }
|
||||
T && value() && noexcept { return std::move(value_); }
|
||||
|
||||
const_error_t & error() const & noexcept { return error_; }
|
||||
E & error() & noexcept { return error_; }
|
||||
const_error_t &&error() const && noexcept { return std::move(error_); }
|
||||
E && error() && noexcept { return std::move(error_); }
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct data_union<void, E> {
|
||||
using const_error_t = typename std::add_const<E>::type;
|
||||
|
||||
alignas(E) std::array<byte, sizeof(E)> error_; ///< the unexpected value
|
||||
|
||||
data_union(data_union const &) = delete;
|
||||
data_union &operator=(data_union const &) = delete;
|
||||
|
||||
data_union(std::nullptr_t) noexcept {}
|
||||
|
||||
template <typename... A>
|
||||
data_union(in_place_t, A &&...) noexcept {}
|
||||
template <typename... A>
|
||||
data_union(unexpected_t, A &&...args) {
|
||||
construct<E>(&error_, std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
void destruct_value() noexcept {}
|
||||
void destruct_error() noexcept { destroy(reinterpret_cast<E *>(&error_)); }
|
||||
|
||||
const_error_t & error() const & noexcept { return *reinterpret_cast<E *>(error_.data()); }
|
||||
E & error() & noexcept { return *reinterpret_cast<E *>(error_.data()); }
|
||||
const_error_t &&error() const && noexcept { return std::move(*reinterpret_cast<E *>(error_.data())); }
|
||||
E && error() && noexcept { return std::move(*reinterpret_cast<E *>(error_.data())); }
|
||||
};
|
||||
|
||||
template <typename T, typename E>
|
||||
auto destruct(bool /*has_value*/, data_union<T, E> &/*data*/) noexcept
|
||||
-> typename std::enable_if<std::is_trivially_destructible<T>::value &&
|
||||
std::is_trivially_destructible<E>::value>::type {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
auto destruct(bool has_value, data_union<T, E> &data) noexcept
|
||||
-> typename std::enable_if<!std::is_trivially_destructible<T>::value &&
|
||||
std::is_trivially_destructible<E>::value>::type {
|
||||
if (has_value) data.destruct_value();
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
auto destruct(bool has_value, data_union<T, E> &data) noexcept
|
||||
-> typename std::enable_if< std::is_trivially_destructible<T>::value &&
|
||||
!std::is_trivially_destructible<E>::value>::type {
|
||||
if (!has_value) data.destruct_error();
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
auto destruct(bool has_value, data_union<T, E> &data) noexcept
|
||||
-> typename std::enable_if<!std::is_trivially_destructible<T>::value &&
|
||||
!std::is_trivially_destructible<E>::value>::type {
|
||||
if (has_value) {
|
||||
data.destruct_value();
|
||||
} else {
|
||||
data.destruct_error();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename S, typename T, typename E>
|
||||
struct value_getter : data_union<T, E> {
|
||||
using data_union<T, E>::data_union;
|
||||
|
||||
template <typename U>
|
||||
value_getter(U &&other) : data_union<T, E>(nullptr) {
|
||||
if (other) {
|
||||
construct<data_union<T, E>>(this, in_place, std::forward<U>(other).value());
|
||||
} else {
|
||||
construct<data_union<T, E>>(this, unexpected, std::forward<U>(other).error());
|
||||
}
|
||||
}
|
||||
|
||||
T const *operator->() const noexcept { return std::addressof(this->value()); }
|
||||
T * operator->() noexcept { return std::addressof(this->value()); }
|
||||
|
||||
T const & operator*() const & noexcept { return this->value(); }
|
||||
T & operator*() & noexcept { return this->value(); }
|
||||
T const &&operator*() const && noexcept { return std::move(this->value()); }
|
||||
T && operator*() && noexcept { return std::move(this->value()); }
|
||||
|
||||
template <typename U>
|
||||
T value_or(U &&def) const & {
|
||||
return bool(*static_cast<S *>(this)) ? **this : static_cast<T>(std::forward<U>(def));
|
||||
}
|
||||
template <typename U>
|
||||
T value_or(U &&def) && {
|
||||
return bool(*static_cast<S *>(this)) ? std::move(**this) : static_cast<T>(std::forward<U>(def));
|
||||
}
|
||||
|
||||
template <typename... A>
|
||||
T &emplace(A &&...args) {
|
||||
static_cast<S *>(this)->reconstruct(in_place, std::forward<A>(args)...);
|
||||
return this->value();
|
||||
}
|
||||
|
||||
void swap(S &other) {
|
||||
if (bool(*static_cast<S *>(this)) && bool(other)) {
|
||||
std::swap(this->value(), other.value());
|
||||
} else if (!*static_cast<S *>(this) && !other) {
|
||||
std::swap(this->error(), other.error());
|
||||
} else if (!*static_cast<S *>(this) && bool(other)) {
|
||||
E err(std::move(this->error()));
|
||||
this->emplace(std::move(other.value()));
|
||||
other.reconstruct(unexpected, std::move(err));
|
||||
} else /*if (bool(*this) && !other)*/ {
|
||||
E err(std::move(other.error()));
|
||||
other.emplace(std::move(this->value()));
|
||||
static_cast<S *>(this)->reconstruct(unexpected, std::move(err));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename S, typename E>
|
||||
struct value_getter<S, void, E> : data_union<void, E> {
|
||||
using data_union<void, E>::data_union;
|
||||
|
||||
template <typename U>
|
||||
value_getter(U &&other) : data_union<void, E>(nullptr) {
|
||||
if (other) {
|
||||
construct<data_union<void, E>>(this, in_place);
|
||||
} else {
|
||||
construct<data_union<void, E>>(this, unexpected, std::forward<U>(other).error());
|
||||
}
|
||||
}
|
||||
|
||||
void emplace() noexcept {
|
||||
static_cast<S *>(this)->reconstruct(in_place);
|
||||
}
|
||||
|
||||
void swap(S &other) {
|
||||
if (bool(*static_cast<S *>(this)) && bool(other)) {
|
||||
return;
|
||||
} else if (!*static_cast<S *>(this) && !other) {
|
||||
std::swap(this->error(), other.error());
|
||||
} else if (!*static_cast<S *>(this) && bool(other)) {
|
||||
E err(std::move(this->error()));
|
||||
this->emplace();
|
||||
other.reconstruct(unexpected, std::move(err));
|
||||
} else /*if (bool(*this) && !other)*/ {
|
||||
E err(std::move(other.error()));
|
||||
other.emplace();
|
||||
static_cast<S *>(this)->reconstruct(unexpected, std::move(err));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Define the expected storage.
|
||||
*/
|
||||
template <typename T, typename E>
|
||||
struct storage : value_getter<storage<T, E>, T, E> {
|
||||
using getter_t = value_getter<storage<T, E>, T, E>;
|
||||
|
||||
bool has_value_;
|
||||
|
||||
template <typename... A>
|
||||
storage(in_place_t, A &&...args)
|
||||
: getter_t(in_place, std::forward<A>(args)...)
|
||||
, has_value_(true) {}
|
||||
|
||||
template <typename... A>
|
||||
storage(unexpected_t, A &&...args)
|
||||
: getter_t(unexpected, std::forward<A>(args)...)
|
||||
, has_value_(false) {}
|
||||
|
||||
storage(storage const &other)
|
||||
: getter_t(other)
|
||||
, has_value_(other.has_value_) {}
|
||||
|
||||
storage(storage &&other)
|
||||
: getter_t(std::move(other))
|
||||
/// After construction, has_value() is equal to other.has_value().
|
||||
, has_value_(other.has_value_) {}
|
||||
|
||||
template <typename T_, typename E_>
|
||||
storage(storage<T_, E_> const &other)
|
||||
: getter_t(other)
|
||||
, has_value_(other.has_value_) {}
|
||||
|
||||
template <typename T_, typename E_>
|
||||
storage(storage<T_, E_> &&other)
|
||||
: getter_t(std::move(other))
|
||||
/// After construction, has_value() is equal to other.has_value().
|
||||
, has_value_(other.has_value_) {}
|
||||
|
||||
bool has_value() const noexcept {
|
||||
return has_value_;
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return this->has_value();
|
||||
}
|
||||
|
||||
protected:
|
||||
friend getter_t;
|
||||
|
||||
template <typename... A>
|
||||
void reconstruct(A &&...args) {
|
||||
destroy(this);
|
||||
construct<storage>(this, std::forward<A>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief The invoke forwarding helper.
|
||||
|
||||
template <typename F, typename... A>
|
||||
auto invoke(F &&f, A &&...args) noexcept(
|
||||
noexcept(std::forward<F>(f)(std::forward<A>(args)...)))
|
||||
-> decltype(std::forward<F>(f)(std::forward<A>(args)...)) {
|
||||
return std::forward<F>(f)(std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
template <typename F, typename... A>
|
||||
auto invoke(F &&f, A &&...args) noexcept(
|
||||
noexcept(std::forward<F>(f)()))
|
||||
-> decltype(std::forward<F>(f)()) {
|
||||
return std::forward<F>(f)();
|
||||
}
|
||||
|
||||
/// \brief and_then helper.
|
||||
|
||||
template <typename E, typename F,
|
||||
typename R = decltype(invoke(std::declval<F>(), *std::declval<E>()))>
|
||||
R and_then(E &&exp, F &&f) {
|
||||
static_assert(is_specialized<expected, R>::value, "F must return an `expected`.");
|
||||
return bool(exp) ? invoke(std::forward<F>(f), *std::forward<E>(exp))
|
||||
: R(unexpected, std::forward<E>(exp).error());
|
||||
}
|
||||
|
||||
/// \brief or_else helper.
|
||||
|
||||
template <typename E, typename F,
|
||||
typename R = decltype(invoke(std::declval<F>(), std::declval<E>().error()))>
|
||||
R or_else(E &&exp, F &&f) {
|
||||
static_assert(is_specialized<expected, R>::value, "F must return an `expected`.");
|
||||
return bool(exp) ? std::forward<E>(exp)
|
||||
: invoke(std::forward<F>(f), std::forward<E>(exp).error());
|
||||
}
|
||||
|
||||
} // namespace detail_expected
|
||||
|
||||
/**
|
||||
* \class template <typename T, typename E> expected
|
||||
* \brief Provides a way to store either of two values.
|
||||
*/
|
||||
template <typename T, typename E>
|
||||
class expected : public detail_expected::storage<typename std::remove_cv<T>::type, E> {
|
||||
public:
|
||||
using value_type = typename std::remove_cv<T>::type;
|
||||
using error_type = E;
|
||||
|
||||
using detail_expected::storage<value_type, E>::storage;
|
||||
|
||||
expected(expected const &) = default;
|
||||
expected(expected &&) = default;
|
||||
|
||||
expected()
|
||||
: detail_expected::storage<value_type, E>(in_place) {}
|
||||
|
||||
expected &operator=(expected other) {
|
||||
this->swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Monadic operations
|
||||
|
||||
template <typename F>
|
||||
auto and_then(F &&f) & {
|
||||
return detail_expected::and_then(*this, std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
auto and_then(F &&f) const & {
|
||||
return detail_expected::and_then(*this, std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
auto and_then(F &&f) && {
|
||||
return detail_expected::and_then(std::move(*this), std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
auto or_else(F &&f) & {
|
||||
return detail_expected::or_else(*this, std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
auto or_else(F &&f) const & {
|
||||
return detail_expected::or_else(*this, std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
auto or_else(F &&f) && {
|
||||
return detail_expected::or_else(std::move(*this), std::forward<F>(f));
|
||||
}
|
||||
};
|
||||
|
||||
// Compares
|
||||
|
||||
template <typename T1, typename E1, typename T2, typename E2>
|
||||
bool operator==(expected<T1, E1> const &lhs, expected<T2, E2> const &rhs) {
|
||||
return (lhs.has_value() == rhs.has_value())
|
||||
&& (lhs.has_value() ? *lhs == *rhs : lhs.error() == rhs.error());
|
||||
}
|
||||
|
||||
template <typename E1, typename E2>
|
||||
bool operator==(expected<void, E1> const &lhs, expected<void, E2> const &rhs) {
|
||||
return (lhs.has_value() == rhs.has_value())
|
||||
&& (lhs.has_value() || lhs.error() == rhs.error());
|
||||
}
|
||||
|
||||
template <typename T1, typename E1, typename T2, typename E2>
|
||||
bool operator!=(expected<T1, E1> const &lhs, expected<T2, E2> const &rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
45
include/libipc/imp/export.h
Executable file
45
include/libipc/imp/export.h
Executable file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* \file libipc/export.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Define the symbol export interfaces.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
|
||||
#if defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
# define LIBIPC_DECL_EXPORT Q_DECL_EXPORT
|
||||
# define LIBIPC_DECL_IMPORT Q_DECL_IMPORT
|
||||
|
||||
#else // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
/**
|
||||
* \brief Compiler & system detection for LIBIPC_DECL_EXPORT & LIBIPC_DECL_IMPORT.
|
||||
* Not using QtCore cause it shouldn't depend on Qt.
|
||||
*/
|
||||
# if defined(LIBIPC_CC_MSVC) || defined(LIBIPC_OS_WIN)
|
||||
# define LIBIPC_DECL_EXPORT __declspec(dllexport)
|
||||
# define LIBIPC_DECL_IMPORT __declspec(dllimport)
|
||||
# elif defined(LIBIPC_OS_ANDROID) || defined(LIBIPC_OS_LINUX) || defined(LIBIPC_CC_GNUC)
|
||||
# define LIBIPC_DECL_EXPORT __attribute__((visibility("default")))
|
||||
# define LIBIPC_DECL_IMPORT __attribute__((visibility("default")))
|
||||
# else
|
||||
# define LIBIPC_DECL_EXPORT __attribute__((visibility("default")))
|
||||
# define LIBIPC_DECL_IMPORT __attribute__((visibility("default")))
|
||||
# endif
|
||||
|
||||
#endif // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
/**
|
||||
* \brief Define LIBIPC_EXPORT for exporting function & class.
|
||||
*/
|
||||
#ifndef LIBIPC_EXPORT
|
||||
# if defined(LIBIPC_LIBRARY_SHARED_BUILDING__)
|
||||
# define LIBIPC_EXPORT LIBIPC_DECL_EXPORT
|
||||
# elif defined(LIBIPC_LIBRARY_SHARED_USING__)
|
||||
# define LIBIPC_EXPORT LIBIPC_DECL_IMPORT
|
||||
# else
|
||||
# define LIBIPC_EXPORT
|
||||
# endif
|
||||
#endif /*LIBIPC_EXPORT*/
|
||||
177
include/libipc/imp/fmt.h
Normal file
177
include/libipc/imp/fmt.h
Normal file
@ -0,0 +1,177 @@
|
||||
/**
|
||||
* \file libipc/fmt.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief String formatting.
|
||||
*
|
||||
* \remarks The current performance is not high,
|
||||
* because I use std::sprintf directly for formatting for convenience.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <chrono> // std::chrono::time_point
|
||||
#include <tuple>
|
||||
#include <cstddef>
|
||||
#include <ctime> // std::tm, std::localtime
|
||||
|
||||
#include "libipc/imp/fmt_cpo.h"
|
||||
#include "libipc/imp/span.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/imp/export.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \brief The format string reference wrapper.
|
||||
*/
|
||||
template <typename T>
|
||||
struct fmt_ref {
|
||||
span<char const> fstr;
|
||||
T param;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Conversion specifiers.
|
||||
*
|
||||
* \remarks Just like printf, the format string is of the form
|
||||
* [flags][field_width][.precision][conversion_character]
|
||||
*
|
||||
* \see http://personal.ee.surrey.ac.uk/Personal/R.Bowden/C/printf.html
|
||||
*/
|
||||
template <std::size_t N>
|
||||
auto spec(char const (&fstr)[N]) noexcept {
|
||||
return [&fstr](auto &&arg) noexcept {
|
||||
using arg_t = decltype(arg);
|
||||
return fmt_ref<arg_t> {{fstr}, static_cast<arg_t>(arg)};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief String formatting function.
|
||||
*
|
||||
* \param args arguments that support the fmt output
|
||||
* \return an empty string if the fmt output fails
|
||||
*/
|
||||
template <typename... A>
|
||||
LIBIPC_NODISCARD std::string fmt(A &&...args) {
|
||||
std::string joined;
|
||||
fmt_context ctx(joined);
|
||||
if (fmt_to(ctx, std::forward<A>(args)...)) {
|
||||
return ctx.finish() ? joined : "";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/// \brief String types.
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char const * a) noexcept;
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, std::string const &a) noexcept;
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char const * a, span<char const> fstr) noexcept;
|
||||
inline bool to_string(fmt_context &ctx, std::string const &a, span<char const> fstr) noexcept { return to_string(ctx, a.c_str(), fstr); }
|
||||
|
||||
/// \brief Character to string conversion.
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char a) noexcept;
|
||||
#if defined(LIBIPC_CPP_20)
|
||||
inline bool to_string(fmt_context &ctx, char8_t a) noexcept { return to_string(ctx, (char)a); }
|
||||
#endif // defined(LIBIPC_CPP_20)
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, wchar_t a) noexcept;
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char16_t a) noexcept;
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char32_t a) noexcept;
|
||||
|
||||
/// \brief Conversion of numeric types to strings.
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, signed short a, span<char const> fstr = {}) noexcept;
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, unsigned short a, span<char const> fstr = {}) noexcept;
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, signed int a, span<char const> fstr = {}) noexcept;
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, unsigned int a, span<char const> fstr = {}) noexcept;
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, signed long a, span<char const> fstr = {}) noexcept;
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, unsigned long a, span<char const> fstr = {}) noexcept;
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, signed long long a, span<char const> fstr = {}) noexcept;
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, unsigned long long a, span<char const> fstr = {}) noexcept;
|
||||
inline bool to_string(fmt_context &ctx, signed char a, span<char const> fstr = {}) noexcept { return to_string(ctx, (int)a, fstr); }
|
||||
inline bool to_string(fmt_context &ctx, unsigned char a, span<char const> fstr = {}) noexcept { return to_string(ctx, (unsigned)a, fstr); }
|
||||
|
||||
/// \brief Conversion of floating point type to strings.
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, double a, span<char const> fstr = {}) noexcept;
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, long double a, span<char const> fstr = {}) noexcept;
|
||||
inline bool to_string(fmt_context &ctx, float a, span<char const> fstr = {}) noexcept { return to_string(ctx, (double)a, fstr); }
|
||||
|
||||
/// \brief Pointer.
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, std::nullptr_t) noexcept;
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, void const volatile *a) noexcept;
|
||||
template <typename T,
|
||||
typename = std::enable_if_t<!std::is_same<T, char>::value>>
|
||||
inline bool to_string(fmt_context &ctx, T const volatile *a) noexcept { return to_string(ctx, (void *)a); }
|
||||
|
||||
/// \brief Date and time.
|
||||
LIBIPC_EXPORT bool to_string(fmt_context &ctx, std::tm const &a, span<char const> fstr = {}) noexcept;
|
||||
|
||||
namespace detail_fmt {
|
||||
|
||||
/**
|
||||
* \brief Convert std::time_t to std::string.
|
||||
* \return an empty string if the conversion fails
|
||||
*/
|
||||
inline bool time_to_string(fmt_context &ctx, std::time_t tt, span<char const> fstr) noexcept {
|
||||
#if defined(LIBIPC_CC_MSVC)
|
||||
/// \see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-s-localtime32-s-localtime64-s
|
||||
std::tm tm{};
|
||||
if (::localtime_s(&tm, &tt) != 0) {
|
||||
return {};
|
||||
}
|
||||
return to_string(ctx, tm, fstr);
|
||||
#else
|
||||
return to_string(ctx, *std::localtime(&tt), fstr);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace detail_fmt
|
||||
|
||||
template <class Clock, class Duration>
|
||||
bool to_string(fmt_context &ctx, std::chrono::time_point<Clock, Duration> const &a, span<char const> fstr = {}) noexcept {
|
||||
return detail_fmt::time_to_string(ctx, std::chrono::system_clock::to_time_t(a), fstr);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Predefined fmt_to method
|
||||
*/
|
||||
namespace detail_tag_invoke {
|
||||
|
||||
template <typename T>
|
||||
auto tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, T &&arg) noexcept
|
||||
-> decltype(ipc::to_string(ctx, std::forward<T>(arg))) {
|
||||
return ipc::to_string(ctx, std::forward<T>(arg));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, fmt_ref<T> arg) noexcept
|
||||
-> decltype(ipc::to_string(ctx, static_cast<T>(arg.param), arg.fstr)) {
|
||||
return ipc::to_string(ctx, static_cast<T>(arg.param), arg.fstr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, span<T> s) {
|
||||
if (s.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (!fmt_to(ctx, s[0])) {
|
||||
return false;
|
||||
}
|
||||
for (std::size_t i = 1; i < s.size(); ++i) {
|
||||
if (!fmt_to(ctx, ' ', s[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Tp, std::size_t... I>
|
||||
bool unfold_tuple_fmt_to(fmt_context &ctx, Tp const &tp, std::index_sequence<I...>) {
|
||||
return fmt_to(ctx, std::get<I>(tp)...);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, std::tuple<T...> const &tp) {
|
||||
return unfold_tuple_fmt_to(ctx, tp, std::index_sequence_for<T...>{});
|
||||
}
|
||||
|
||||
} // namespace detail_tag_invoke
|
||||
} // namespace ipc
|
||||
66
include/libipc/imp/fmt_cpo.h
Normal file
66
include/libipc/imp/fmt_cpo.h
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* \file libipc/fmt_cpo.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief String formatting CPO.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
|
||||
#include "libipc/imp/generic.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/imp/span.h"
|
||||
#include "libipc/imp/export.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \class class LIBIPC_EXPORT fmt_context
|
||||
* \brief The context of fmt.
|
||||
*/
|
||||
class LIBIPC_EXPORT fmt_context {
|
||||
std::array<char, 2048U> sbuf_; ///< stack buffer
|
||||
|
||||
std::string &joined_;
|
||||
std::size_t offset_;
|
||||
|
||||
public:
|
||||
fmt_context(std::string &j) noexcept;
|
||||
|
||||
std::size_t capacity() noexcept;
|
||||
void reset() noexcept;
|
||||
bool finish() noexcept;
|
||||
span<char> buffer(std::size_t sz) noexcept;
|
||||
void expend(std::size_t sz) noexcept;
|
||||
bool append(span<char const> const &str) noexcept;
|
||||
};
|
||||
|
||||
/// \brief Supports custom fmt_to methods for ipc::fmt.
|
||||
namespace detail_tag_invoke {
|
||||
|
||||
class fmt_to_t {
|
||||
template <typename A1>
|
||||
bool get_result(fmt_context &ctx, A1 && a1) const {
|
||||
return ipc::tag_invoke(fmt_to_t{}, ctx, std::forward<A1>(a1));
|
||||
}
|
||||
|
||||
template <typename A1, typename... A>
|
||||
bool get_result(fmt_context &ctx, A1 && a1, A &&...args) const {
|
||||
return get_result(ctx, std::forward<A1>(a1))
|
||||
&& get_result(ctx, std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename... A>
|
||||
bool operator()(fmt_context &ctx, A &&...args) const {
|
||||
return get_result(ctx, std::forward<A>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail_tag_invoke
|
||||
|
||||
constexpr detail_tag_invoke::fmt_to_t fmt_to{};
|
||||
|
||||
} // namespace ipc
|
||||
301
include/libipc/imp/generic.h
Normal file
301
include/libipc/imp/generic.h
Normal file
@ -0,0 +1,301 @@
|
||||
/**
|
||||
* \file libipc/generic.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Tools for generic programming.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <type_traits> // std::declval, std::true_type, std::false_type
|
||||
#include <cstddef> // std::size_t
|
||||
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \brief Utility metafunction that maps a sequence of any types to the type void
|
||||
* \see https://en.cppreference.com/w/cpp/types/void_t
|
||||
*/
|
||||
template <typename...>
|
||||
using void_t = void;
|
||||
|
||||
/**
|
||||
* \brief A type-list for generic programming.
|
||||
*/
|
||||
template <typename...>
|
||||
struct types {};
|
||||
|
||||
/**
|
||||
* \brief To indicate that the contained object should be constructed in-place.
|
||||
* \see https://en.cppreference.com/w/cpp/utility/in_place
|
||||
*/
|
||||
#if defined(LIBIPC_CPP_17)
|
||||
using std::in_place_t;
|
||||
using std::in_place;
|
||||
#else /*!LIBIPC_CPP_17*/
|
||||
struct in_place_t {
|
||||
explicit in_place_t() = default;
|
||||
};
|
||||
constexpr in_place_t in_place{};
|
||||
#endif/*!LIBIPC_CPP_17*/
|
||||
|
||||
/**
|
||||
* \brief A general pattern for supporting customisable functions
|
||||
* \see https://www.open-std.org/jtc1/sc22/WG21/docs/papers/2019/p1895r0.pdf
|
||||
*/
|
||||
namespace detail_tag_invoke {
|
||||
|
||||
void tag_invoke();
|
||||
|
||||
struct tag_invoke_t {
|
||||
template <typename T, typename... A>
|
||||
constexpr auto operator()(T tag, A &&...args) const
|
||||
noexcept(noexcept(tag_invoke(std::forward<T>(tag), std::forward<A>(args)...)))
|
||||
-> decltype(tag_invoke(std::forward<T>(tag), std::forward<A>(args)...)) {
|
||||
return tag_invoke(std::forward<T>(tag), std::forward<A>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail_tag_invoke
|
||||
|
||||
constexpr detail_tag_invoke::tag_invoke_t tag_invoke{};
|
||||
|
||||
/**
|
||||
* \brief Circumventing forwarding reference may override copy and move constructs.
|
||||
* \see https://mpark.github.io/programming/2014/06/07/beware-of-perfect-forwarding-constructors/
|
||||
*/
|
||||
namespace detail_not_match {
|
||||
|
||||
template <typename T, typename... A>
|
||||
struct is_same_first : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_same_first<T, T> : std::true_type {};
|
||||
|
||||
} // namespace detail_not_match
|
||||
|
||||
template <typename T, typename... A>
|
||||
using not_match =
|
||||
typename std::enable_if<!detail_not_match::is_same_first<T,
|
||||
typename std::decay<A>::type...>::value, bool>::type;
|
||||
|
||||
/**
|
||||
* \brief Determines whether a type is specialized from a particular template.
|
||||
*/
|
||||
template <template <typename...> class Tt, typename T>
|
||||
struct is_specialized : std::false_type {};
|
||||
|
||||
template <template <typename...> class Tt, typename... A>
|
||||
struct is_specialized<Tt, Tt<A...>> : std::true_type {};
|
||||
|
||||
/**
|
||||
* \brief Copy the cv qualifier and reference of the source type to the target type.
|
||||
*/
|
||||
template <typename Src, typename Des>
|
||||
struct copy_cvref {
|
||||
using type = Des;
|
||||
};
|
||||
|
||||
template <typename Src, typename Des>
|
||||
struct copy_cvref<Src const, Des> {
|
||||
using type = typename std::add_const<Des>::type;
|
||||
};
|
||||
|
||||
template <typename Src, typename Des>
|
||||
struct copy_cvref<Src volatile, Des> {
|
||||
using type = typename std::add_volatile<Des>::type;
|
||||
};
|
||||
|
||||
template <typename Src, typename Des>
|
||||
struct copy_cvref<Src const volatile, Des> {
|
||||
using type = typename std::add_cv<Des>::type;
|
||||
};
|
||||
|
||||
template <typename Src, typename Des>
|
||||
struct copy_cvref<Src &, Des> {
|
||||
using type = typename std::add_lvalue_reference<
|
||||
typename copy_cvref<Src, Des>::type>::type;
|
||||
};
|
||||
|
||||
template <typename Src, typename Des>
|
||||
struct copy_cvref<Src &&, Des> {
|
||||
using type = typename std::add_rvalue_reference<
|
||||
typename copy_cvref<Src, Des>::type>::type;
|
||||
};
|
||||
|
||||
template <typename Src, typename Des>
|
||||
using copy_cvref_t = typename copy_cvref<Src, Des>::type;
|
||||
|
||||
/**
|
||||
* \brief Returns the size of the given range.
|
||||
* \see https://en.cppreference.com/w/cpp/iterator/size
|
||||
*/
|
||||
namespace detail_countof {
|
||||
|
||||
template <typename T>
|
||||
struct trait_has_size {
|
||||
private:
|
||||
template <typename Type>
|
||||
static std::true_type check(decltype(std::declval<Type>().size())*);
|
||||
template <typename Type>
|
||||
static std::false_type check(...);
|
||||
public:
|
||||
using type = decltype(check<T>(nullptr));
|
||||
static constexpr auto value = type::value;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct trait_has_Size {
|
||||
private:
|
||||
template <typename Type>
|
||||
static std::true_type check(decltype(std::declval<Type>().Size())*);
|
||||
template <typename Type>
|
||||
static std::false_type check(...);
|
||||
public:
|
||||
using type = decltype(check<T>(nullptr));
|
||||
static constexpr auto value = type::value;
|
||||
};
|
||||
|
||||
template <typename C, bool = trait_has_size<C>::value
|
||||
, bool = trait_has_Size<C>::value>
|
||||
struct trait;
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
struct trait<T[N], false, false> {
|
||||
static constexpr auto countof(T const (&)[N]) noexcept {
|
||||
return N;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename C, bool B>
|
||||
struct trait<C, true, B> {
|
||||
static constexpr auto countof(C const &c) noexcept(noexcept(c.size())) {
|
||||
return c.size();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
struct trait<C, false, true> {
|
||||
static constexpr auto countof(C const &c) noexcept(noexcept(c.Size())) {
|
||||
return c.Size();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail_countof
|
||||
|
||||
template <typename C,
|
||||
typename T = detail_countof::trait<C>,
|
||||
typename R = decltype(T::countof(std::declval<C const &>()))>
|
||||
constexpr R countof(C const &c) noexcept(noexcept(T::countof(c))) {
|
||||
return T::countof(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the data pointer of the given range.
|
||||
* \see https://en.cppreference.com/w/cpp/iterator/data
|
||||
*/
|
||||
|
||||
namespace detail_dataof {
|
||||
|
||||
template <typename T>
|
||||
struct trait_has_data {
|
||||
private:
|
||||
template <typename Type>
|
||||
static std::true_type check(decltype(std::declval<Type>().data())*);
|
||||
template <typename Type>
|
||||
static std::false_type check(...);
|
||||
public:
|
||||
using type = decltype(check<T>(nullptr));
|
||||
static constexpr auto value = type::value;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct trait_has_Data {
|
||||
private:
|
||||
template <typename Type>
|
||||
static std::true_type check(decltype(std::declval<Type>().Data())*);
|
||||
template <typename Type>
|
||||
static std::false_type check(...);
|
||||
public:
|
||||
using type = decltype(check<T>(nullptr));
|
||||
static constexpr auto value = type::value;
|
||||
};
|
||||
|
||||
template <typename C, bool = trait_has_data<C>::value
|
||||
, bool = trait_has_Data<C>::value>
|
||||
struct trait;
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
struct trait<T[N], false, false> {
|
||||
static constexpr T const *dataof(T const (&arr)[N]) noexcept {
|
||||
return arr;
|
||||
}
|
||||
static constexpr T *dataof(T (&arr)[N]) noexcept {
|
||||
return arr;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename C, bool B>
|
||||
struct trait<C, true, B> {
|
||||
template <typename T>
|
||||
static constexpr auto dataof(T &&c) noexcept(noexcept(c.data())) {
|
||||
return std::forward<T>(c).data();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
struct trait<C, false, true> {
|
||||
template <typename T>
|
||||
static constexpr auto dataof(T &&c) noexcept(noexcept(c.Data())) {
|
||||
return std::forward<T>(c).Data();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
struct trait<C, false, false> {
|
||||
template <typename T>
|
||||
static constexpr T const *dataof(std::initializer_list<T> il) noexcept {
|
||||
return il.begin();
|
||||
}
|
||||
template <typename T>
|
||||
static constexpr T const *dataof(T const *p) noexcept {
|
||||
return p;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail_dataof
|
||||
|
||||
template <typename C,
|
||||
typename T = detail_dataof::trait<std::remove_cv_t<std::remove_reference_t<C>>>,
|
||||
typename R = decltype(T::dataof(std::declval<C>()))>
|
||||
constexpr R dataof(C &&c) noexcept(noexcept(T::dataof(std::forward<C>(c)))) {
|
||||
return T::dataof(std::forward<C>(c));
|
||||
}
|
||||
|
||||
/// \brief Returns after converting the value to the underlying type of E.
|
||||
/// \see https://en.cppreference.com/w/cpp/types/underlying_type
|
||||
/// https://en.cppreference.com/w/cpp/utility/to_underlying
|
||||
template <typename E>
|
||||
constexpr auto underlyof(E e) noexcept {
|
||||
return static_cast<std::underlying_type_t<E>>(e);
|
||||
}
|
||||
|
||||
namespace detail_horrible_cast {
|
||||
|
||||
template <typename T, typename U>
|
||||
union temp {
|
||||
U in;
|
||||
T out;
|
||||
};
|
||||
|
||||
} // namespace detail_horrible_cast
|
||||
|
||||
template <typename T, typename U>
|
||||
constexpr auto horrible_cast(U &&in) noexcept
|
||||
-> typename std::enable_if<std::is_trivially_copyable<T>::value
|
||||
&& std::is_trivially_copyable<std::decay_t<U>>::value, T>::type {
|
||||
return detail_horrible_cast::temp<T, std::decay_t<U>>{std::forward<U>(in)}.out;
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
195
include/libipc/imp/log.h
Normal file
195
include/libipc/imp/log.h
Normal file
@ -0,0 +1,195 @@
|
||||
/**
|
||||
* \file libipc/log.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Simple log output component.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <chrono>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <exception>
|
||||
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/imp/fmt.h"
|
||||
#include "libipc/imp/generic.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace log {
|
||||
|
||||
enum class level : std::int32_t {
|
||||
trace,
|
||||
debug,
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
failed,
|
||||
};
|
||||
|
||||
/// \struct template <typename... T> struct context
|
||||
/// \brief Logging context.
|
||||
/// \tparam ...T - a log records all parameter types passed
|
||||
template <typename... T>
|
||||
struct context {
|
||||
log::level level;
|
||||
std::chrono::system_clock::time_point tp;
|
||||
char const *func;
|
||||
std::tuple<T...> params;
|
||||
};
|
||||
|
||||
/// \brief Custom defined fmt_to method for imp::fmt
|
||||
namespace detail_log {
|
||||
|
||||
template <typename Tp, std::size_t... I, typename... A>
|
||||
bool unfold_tuple_fmt_to(fmt_context &ctx, Tp const &tp, std::index_sequence<I...>, A &&...args) {
|
||||
return fmt_to(ctx, std::forward<A>(args)..., std::get<I>(tp)...);
|
||||
}
|
||||
|
||||
} // namespace detail_log
|
||||
|
||||
template <typename... T>
|
||||
bool context_to_string(fmt_context &f_ctx, context<T...> const &l_ctx) {
|
||||
static constexpr char types[] = {
|
||||
'T', 'D', 'I', 'W', 'E', 'F',
|
||||
};
|
||||
auto ms = std::chrono::time_point_cast<std::chrono::milliseconds>(l_ctx.tp).time_since_epoch().count() % 1000;
|
||||
return detail_log::unfold_tuple_fmt_to(f_ctx, l_ctx.params, std::index_sequence_for<T...>{},
|
||||
"[", types[underlyof(l_ctx.level)], "]"
|
||||
"[", l_ctx.tp, ".", spec("03")(ms), "]"
|
||||
"[", l_ctx.func, "] ");
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
std::string context_to_string(context<T...> const &l_ctx) {
|
||||
std::string log_txt;
|
||||
fmt_context f_ctx(log_txt);
|
||||
LIBIPC_TRY {
|
||||
if (!context_to_string(f_ctx, l_ctx)) {
|
||||
return {};
|
||||
}
|
||||
f_ctx.finish();
|
||||
return log_txt;
|
||||
} LIBIPC_CATCH(...) {
|
||||
f_ctx.finish();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Standard console output object.
|
||||
inline auto &make_std_out() noexcept {
|
||||
static auto std_out = [](auto const &ctx) {
|
||||
auto s = context_to_string(ctx);
|
||||
switch (ctx.level) {
|
||||
case level::trace:
|
||||
case level::debug:
|
||||
case level::info:
|
||||
std::fprintf(stdout, "%s\n", s.c_str());
|
||||
break;
|
||||
case level::warning:
|
||||
case level::error:
|
||||
case level::failed:
|
||||
std::fprintf(stderr, "%s\n", s.c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
return std_out;
|
||||
}
|
||||
|
||||
/// \brief Get the exception information.
|
||||
inline char const *exception_string(std::exception_ptr eptr) noexcept {
|
||||
LIBIPC_TRY {
|
||||
if (eptr) {
|
||||
std::rethrow_exception(eptr);
|
||||
}
|
||||
} LIBIPC_CATCH(std::exception const &e) {
|
||||
return e.what();
|
||||
} LIBIPC_CATCH(...) {
|
||||
return "unknown";
|
||||
}
|
||||
return "none";
|
||||
}
|
||||
|
||||
/// \brief Record the last information when an exception occurs.
|
||||
inline void exception_print(char const *func, std::exception_ptr eptr) noexcept {
|
||||
if (func == nullptr) {
|
||||
func = "-";
|
||||
}
|
||||
std::fprintf(stderr, "[F][%s] exception: %s\n", func, exception_string(eptr));
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Log information base class.
|
||||
*/
|
||||
class logger_base {
|
||||
protected:
|
||||
char const *func_;
|
||||
level level_limit_;
|
||||
|
||||
logger_base(char const *func, level level_limit) noexcept
|
||||
: func_ (func)
|
||||
, level_limit_(level_limit) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Log information grips.
|
||||
*/
|
||||
template <typename Outputer>
|
||||
class logger : public logger_base {
|
||||
Outputer out_;
|
||||
|
||||
public:
|
||||
template <typename O>
|
||||
logger(char const *func, O &&out, level level_limit) noexcept
|
||||
: logger_base(func, level_limit)
|
||||
, out_ (std::forward<O>(out)) {}
|
||||
|
||||
template <typename... A>
|
||||
logger const &operator()(log::level l, A &&...args) const noexcept {
|
||||
if (underlyof(l) < underlyof(level_limit_)) {
|
||||
return *this;
|
||||
}
|
||||
LIBIPC_TRY {
|
||||
out_(context<A &&...> {
|
||||
l, std::chrono::system_clock::now(), func_,
|
||||
std::forward_as_tuple(std::forward<A>(args)...),
|
||||
});
|
||||
} LIBIPC_CATCH(...) {
|
||||
exception_print(func_, std::current_exception());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename... A> logger const &trace (A &&...args) const noexcept { return (*this)(log::level::trace , std::forward<A>(args)...); }
|
||||
template <typename... A> logger const &debug (A &&...args) const noexcept { return (*this)(log::level::debug , std::forward<A>(args)...); }
|
||||
template <typename... A> logger const &info (A &&...args) const noexcept { return (*this)(log::level::info , std::forward<A>(args)...); }
|
||||
template <typename... A> logger const &warning(A &&...args) const noexcept { return (*this)(log::level::warning, std::forward<A>(args)...); }
|
||||
template <typename... A> logger const &error (A &&...args) const noexcept { return (*this)(log::level::error , std::forward<A>(args)...); }
|
||||
template <typename... A> logger const &failed (A &&...args) const noexcept { return (*this)(log::level::failed , std::forward<A>(args)...); }
|
||||
};
|
||||
|
||||
template <typename O>
|
||||
inline auto make_logger(char const *func, O &&out, level level_limit = level::info) noexcept {
|
||||
return logger<std::decay_t<O>>(func, std::forward<O>(out), level_limit);
|
||||
}
|
||||
|
||||
inline auto make_logger(char const *func, level level_limit = level::info) noexcept {
|
||||
return make_logger(func, make_std_out(), level_limit);
|
||||
}
|
||||
|
||||
inline auto make_logger(char const * /*ignore*/, char const *name, level level_limit = level::info) noexcept {
|
||||
return make_logger(name, make_std_out(), level_limit);
|
||||
}
|
||||
|
||||
#define LIBIPC_LOG(...) \
|
||||
auto log \
|
||||
= [](auto &&...args) noexcept { \
|
||||
return ::ipc::log::make_logger(__func__, std::forward<decltype(args)>(args)...); \
|
||||
}(__VA_ARGS__)
|
||||
|
||||
} // namespace log
|
||||
} // namespace ipc
|
||||
43
include/libipc/imp/nameof.h
Normal file
43
include/libipc/imp/nameof.h
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* \file libipc/nameof.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Gets the name string of a type.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <typeinfo>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/imp/span.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \brief The conventional way to obtain demangled symbol name.
|
||||
* \see https://www.boost.org/doc/libs/1_80_0/libs/core/doc/html/core/demangle.html
|
||||
*
|
||||
* \param name the mangled name
|
||||
* \return std::string a human-readable demangled type name
|
||||
*/
|
||||
LIBIPC_EXPORT std::string demangle(std::string name) noexcept;
|
||||
|
||||
/**
|
||||
* \brief Returns an implementation defined string containing the name of the type.
|
||||
* \see https://en.cppreference.com/w/cpp/types/type_info/name
|
||||
*
|
||||
* \tparam T a type
|
||||
* \return std::string a human-readable demangled type name
|
||||
*/
|
||||
template <typename T>
|
||||
std::string nameof() noexcept {
|
||||
LIBIPC_TRY {
|
||||
return demangle(typeid(T).name());
|
||||
} LIBIPC_CATCH(...) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
138
include/libipc/imp/result.h
Normal file
138
include/libipc/imp/result.h
Normal file
@ -0,0 +1,138 @@
|
||||
/**
|
||||
* \file libipc/result.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Define the return value type with an error status code.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
|
||||
#include "libipc/imp/expected.h"
|
||||
#include "libipc/imp/error.h"
|
||||
#include "libipc/imp/generic.h"
|
||||
#include "libipc/imp/fmt.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail_result {
|
||||
|
||||
template <typename T>
|
||||
struct generic_initializer {
|
||||
using storage_t = expected<T, std::error_code>;
|
||||
|
||||
/// \brief Custom initialization.
|
||||
static constexpr storage_t init_code() noexcept {
|
||||
return {unexpected, std::error_code(-1, std::generic_category())};
|
||||
}
|
||||
|
||||
static constexpr storage_t init_code(T value) noexcept {
|
||||
return {in_place, value};
|
||||
}
|
||||
|
||||
static constexpr storage_t init_code(std::error_code const &ec) noexcept {
|
||||
return {unexpected, ec};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct result_base;
|
||||
|
||||
template <typename T>
|
||||
struct result_base<T, std::enable_if_t<std::is_pointer<T>::value>>
|
||||
: generic_initializer<T> {
|
||||
|
||||
using storage_t = typename generic_initializer<T>::storage_t;
|
||||
using generic_initializer<T>::init_code;
|
||||
|
||||
static constexpr storage_t init_code(std::nullptr_t, std::error_code const &ec) noexcept {
|
||||
return {unexpected, ec};
|
||||
}
|
||||
|
||||
static constexpr storage_t init_code(std::nullptr_t) noexcept {
|
||||
return {unexpected, std::error_code(-1, std::generic_category())};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct result_base<T, std::enable_if_t<std::is_integral<T>::value || std::is_enum<T>::value>>
|
||||
: generic_initializer<T> {
|
||||
|
||||
using storage_t = typename generic_initializer<T>::storage_t;
|
||||
using generic_initializer<T>::init_code;
|
||||
};
|
||||
|
||||
} // namespace detail_result
|
||||
|
||||
/**
|
||||
* \class class result
|
||||
* \brief The generic wrapper for the result type.
|
||||
*/
|
||||
template <typename T>
|
||||
class result : public detail_result::result_base<T> {
|
||||
private:
|
||||
using base_t = detail_result::result_base<T>;
|
||||
using storage_t = typename base_t::storage_t;
|
||||
|
||||
storage_t ret_; ///< internal data
|
||||
|
||||
public:
|
||||
template <typename... A,
|
||||
typename = not_match<result, A...>,
|
||||
typename = decltype(base_t::init_code(std::declval<A>()...))>
|
||||
result(A &&...args) noexcept
|
||||
: ret_(base_t::init_code(std::forward<A>(args)...)) {}
|
||||
|
||||
std::string format_string() const {
|
||||
if LIBIPC_LIKELY(ret_) {
|
||||
return fmt("value = ", ret_.value());
|
||||
} else {
|
||||
return fmt("error = ", ret_.error());
|
||||
}
|
||||
}
|
||||
|
||||
T value() const noexcept { return ret_ ? ret_.value() : T{}; }
|
||||
bool ok () const noexcept { return ret_.has_value(); }
|
||||
std::error_code error() const noexcept { return ret_.error(); }
|
||||
|
||||
T operator * () const noexcept { return value(); }
|
||||
explicit operator bool() const noexcept { return ok (); }
|
||||
|
||||
friend bool operator==(result const &lhs, result const &rhs) noexcept { return lhs.ret_ == rhs.ret_; }
|
||||
friend bool operator!=(result const &lhs, result const &rhs) noexcept { return !(lhs == rhs); }
|
||||
};
|
||||
|
||||
template <>
|
||||
class result<void> {
|
||||
private:
|
||||
std::error_code ret_; ///< internal data
|
||||
|
||||
public:
|
||||
result() noexcept
|
||||
: ret_(-1, std::generic_category()) {}
|
||||
|
||||
result(std::error_code const &ec) noexcept
|
||||
: ret_(ec) {}
|
||||
|
||||
std::string format_string() const {
|
||||
return fmt("error = ", error());
|
||||
}
|
||||
|
||||
bool ok () const noexcept { return !ret_; }
|
||||
std::error_code error() const noexcept { return ret_; }
|
||||
explicit operator bool () const noexcept { return ok(); }
|
||||
|
||||
friend bool operator==(result const &lhs, result const &rhs) noexcept { return lhs.ret_ == rhs.ret_; }
|
||||
friend bool operator!=(result const &lhs, result const &rhs) noexcept { return !(lhs == rhs); }
|
||||
};
|
||||
|
||||
/// \brief Custom defined fmt_to method for imp::fmt
|
||||
namespace detail_tag_invoke {
|
||||
|
||||
template <typename T>
|
||||
inline bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, result<T> const &r) {
|
||||
return fmt_to(ctx, (r ? "succ" : "fail"), ", ", r.format_string());
|
||||
}
|
||||
|
||||
} // namespace detail_tag_invoke
|
||||
} // namespace ipc
|
||||
77
include/libipc/imp/scope_exit.h
Normal file
77
include/libipc/imp/scope_exit.h
Normal file
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* \file libipc/scope_exit.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Execute guard function when the enclosing scope exits.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <utility> // std::forward, std::move
|
||||
#include <functional> // std::function
|
||||
#include <type_traits>
|
||||
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
template <typename F = std::function<void()>>
|
||||
class scope_exit {
|
||||
F destructor_;
|
||||
mutable bool released_;
|
||||
|
||||
public:
|
||||
template <typename G>
|
||||
explicit scope_exit(G &&destructor) noexcept
|
||||
: destructor_(std::forward<G>(destructor))
|
||||
, released_ (false) {}
|
||||
|
||||
scope_exit(scope_exit &&other) noexcept
|
||||
: destructor_(std::move(other.destructor_))
|
||||
, released_ (std::exchange(other.released_, true)) /*release rhs*/ {}
|
||||
|
||||
scope_exit &operator=(scope_exit &&other) noexcept {
|
||||
destructor_ = std::move(other.destructor_);
|
||||
released_ = std::exchange(other.released_, true);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~scope_exit() noexcept {
|
||||
if (!released_) destructor_();
|
||||
}
|
||||
|
||||
void release() const noexcept {
|
||||
released_ = true;
|
||||
}
|
||||
|
||||
void do_exit() noexcept {
|
||||
if (released_) return;
|
||||
destructor_();
|
||||
released_ = true;
|
||||
}
|
||||
|
||||
void swap(scope_exit &other) noexcept {
|
||||
std::swap(destructor_, other.destructor_);
|
||||
std::swap(released_ , other.released_);
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Creates a scope_exit object.
|
||||
template <typename F>
|
||||
auto make_scope_exit(F &&destructor) noexcept {
|
||||
return scope_exit<std::decay_t<F>>(std::forward<F>(destructor));
|
||||
}
|
||||
|
||||
namespace detail_scope_exit {
|
||||
|
||||
struct scope_exit_helper {
|
||||
template <typename F>
|
||||
auto operator=(F &&f) const noexcept {
|
||||
return make_scope_exit(std::forward<F>(f));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail_scope_exit
|
||||
|
||||
#define LIBIPC_SCOPE_EXIT($VAL) \
|
||||
LIBIPC_UNUSED auto $VAL = ::ipc::detail_scope_exit::scope_exit_helper{}
|
||||
|
||||
} // namespace ipc
|
||||
281
include/libipc/imp/span.h
Normal file
281
include/libipc/imp/span.h
Normal file
@ -0,0 +1,281 @@
|
||||
/**
|
||||
* \file libipc/span.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Describes an object that can refer to a contiguous sequence of objects.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/imp/generic.h"
|
||||
|
||||
#if defined(LIBIPC_CPP_20) && defined(__cpp_lib_span)
|
||||
#include <span>
|
||||
#define LIBIPC_CPP_LIB_SPAN_
|
||||
#endif // __cpp_lib_span
|
||||
|
||||
namespace ipc {
|
||||
namespace detail_span {
|
||||
|
||||
/// \brief Helper trait for span.
|
||||
|
||||
template <typename From, typename To>
|
||||
using array_convertible = std::is_convertible<From(*)[], To(*)[]>;
|
||||
|
||||
template <typename From, typename To>
|
||||
using is_array_convertible =
|
||||
typename std::enable_if<array_convertible<From, To>::value>::type;
|
||||
|
||||
template <typename T, typename Ref>
|
||||
using compatible_ref = array_convertible<typename std::remove_reference<Ref>::type, T>;
|
||||
|
||||
template <typename It>
|
||||
using iter_reference_t = decltype(*std::declval<It&>());
|
||||
|
||||
template <typename T, typename It>
|
||||
using is_compatible_iter =
|
||||
typename std::enable_if<compatible_ref<T, iter_reference_t<It>>::value>::type;
|
||||
|
||||
template <typename From, typename To>
|
||||
using is_inconvertible =
|
||||
typename std::enable_if<!std::is_convertible<From, To>::value>::type;
|
||||
|
||||
template <typename S, typename I>
|
||||
using is_sized_sentinel_for =
|
||||
typename std::enable_if<std::is_convertible<decltype(std::declval<S>() - std::declval<I>()),
|
||||
std::ptrdiff_t>::value>::type;
|
||||
|
||||
template <typename T>
|
||||
using is_continuous_container =
|
||||
decltype(dataof(std::declval<T>()), countof(std::declval<T>()));
|
||||
|
||||
/// \brief Obtain the address represented by p
|
||||
/// without forming a reference to the object pointed to by p.
|
||||
/// \see https://en.cppreference.com/w/cpp/memory/to_address
|
||||
|
||||
template<typename T>
|
||||
constexpr T *to_address(T *ptr) noexcept {
|
||||
static_assert(!std::is_function<T>::value, "ptr shouldn't a function pointer");
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr auto to_address(T const &ptr)
|
||||
noexcept(noexcept(ptr.operator->()))
|
||||
-> decltype(ptr.operator->()) {
|
||||
return to_address(ptr.operator->());
|
||||
}
|
||||
|
||||
} // namespace detail_span
|
||||
|
||||
/**
|
||||
* \brief A simple implementation of span.
|
||||
* \see https://en.cppreference.com/w/cpp/container/span
|
||||
*/
|
||||
template <typename T>
|
||||
class span {
|
||||
public:
|
||||
using element_type = T;
|
||||
using value_type = typename std::remove_cv<element_type>::type;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = element_type *;
|
||||
using const_pointer = typename std::remove_const<element_type>::type const *;
|
||||
using reference = element_type &;
|
||||
using const_reference = typename std::remove_const<element_type>::type const &;
|
||||
using iterator = pointer;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
|
||||
private:
|
||||
pointer ptr_ {nullptr};
|
||||
size_type extent_ {0};
|
||||
|
||||
public:
|
||||
constexpr span() noexcept = default;
|
||||
constexpr span(span const &) noexcept = default;
|
||||
#if (LIBIPC_CC_MSVC > LIBIPC_CC_MSVC_2015)
|
||||
constexpr
|
||||
#endif
|
||||
span & operator=(span const &) noexcept = default;
|
||||
|
||||
template <typename It,
|
||||
typename = detail_span::is_compatible_iter<T, It>>
|
||||
constexpr span(It first, size_type count) noexcept
|
||||
: ptr_ (detail_span::to_address(first))
|
||||
, extent_(count) {}
|
||||
|
||||
template <typename It, typename End,
|
||||
typename = detail_span::is_compatible_iter<T, It>,
|
||||
typename = detail_span::is_sized_sentinel_for<End, It>,
|
||||
typename = detail_span::is_inconvertible<End, size_type>>
|
||||
constexpr span(It first, End last) noexcept(noexcept(last - first))
|
||||
: ptr_ (detail_span::to_address(first))
|
||||
, extent_(static_cast<size_type>(last - first)) {}
|
||||
|
||||
template <typename U,
|
||||
typename = detail_span::is_continuous_container<U>,
|
||||
typename = detail_span::is_array_convertible<std::remove_pointer_t<
|
||||
decltype(dataof(std::declval<U>()))>, element_type>>
|
||||
constexpr span(U &&u) noexcept(noexcept(dataof (std::forward<U>(u)),
|
||||
countof(std::forward<U>(u))))
|
||||
: ptr_ (dataof (std::forward<U>(u)))
|
||||
, extent_(countof(std::forward<U>(u))) {}
|
||||
|
||||
template <typename U, std::size_t E,
|
||||
typename = detail_span::is_array_convertible<U, element_type>>
|
||||
constexpr span(U (&arr)[E]) noexcept
|
||||
: span(static_cast<pointer>(arr), E) {}
|
||||
|
||||
template <typename U, std::size_t E,
|
||||
typename = detail_span::is_array_convertible<U, element_type>>
|
||||
constexpr span(std::array<U, E> &arr) noexcept
|
||||
: span(static_cast<pointer>(arr.data()), E) {}
|
||||
|
||||
template <typename U, std::size_t E,
|
||||
typename = detail_span::is_array_convertible<typename std::add_const<U>::type, element_type>>
|
||||
constexpr span(std::array<U, E> const &arr) noexcept
|
||||
: span(static_cast<pointer>(arr.data()), E) {}
|
||||
|
||||
template <typename U,
|
||||
typename = detail_span::is_array_convertible<U, T>>
|
||||
constexpr span(span<U> const &s) noexcept
|
||||
: ptr_ (s.data())
|
||||
, extent_(s.size()) {}
|
||||
|
||||
#ifdef LIBIPC_CPP_LIB_SPAN_
|
||||
template <typename U, std::size_t E,
|
||||
typename = detail_span::is_array_convertible<U, T>>
|
||||
constexpr span(std::span<U, E> const &s) noexcept
|
||||
: ptr_ (s.data())
|
||||
, extent_(s.size()) {}
|
||||
#endif // LIBIPC_CPP_LIB_SPAN_
|
||||
|
||||
constexpr size_type size() const noexcept {
|
||||
return extent_;
|
||||
}
|
||||
|
||||
constexpr size_type size_bytes() const noexcept {
|
||||
return size() * sizeof(element_type);
|
||||
}
|
||||
|
||||
constexpr bool empty() const noexcept {
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
constexpr pointer data() const noexcept {
|
||||
return this->ptr_;
|
||||
}
|
||||
|
||||
constexpr reference front() const noexcept {
|
||||
return *data();
|
||||
}
|
||||
|
||||
constexpr reference back() const noexcept {
|
||||
return *(data() + (size() - 1));
|
||||
}
|
||||
|
||||
constexpr reference operator[](size_type idx) const noexcept {
|
||||
return *(data() + idx);
|
||||
}
|
||||
|
||||
constexpr iterator begin() const noexcept {
|
||||
return iterator(data());
|
||||
}
|
||||
|
||||
constexpr iterator end() const noexcept {
|
||||
return iterator(data() + this->size());
|
||||
}
|
||||
|
||||
constexpr reverse_iterator rbegin() const noexcept {
|
||||
return reverse_iterator(this->end());
|
||||
}
|
||||
|
||||
constexpr reverse_iterator rend() const noexcept {
|
||||
return reverse_iterator(this->begin());
|
||||
}
|
||||
|
||||
constexpr span first(size_type count) const noexcept {
|
||||
return span(begin(), count);
|
||||
}
|
||||
|
||||
constexpr span last(size_type count) const noexcept {
|
||||
return span(end() - count, count);
|
||||
}
|
||||
|
||||
constexpr span subspan(size_type offset, size_type count = (std::numeric_limits<size_type>::max)()) const noexcept {
|
||||
return (offset >= size()) ? span() : span(begin() + offset, (std::min)(size() - offset, count));
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Support for span equals comparison.
|
||||
|
||||
template <typename T, typename U,
|
||||
typename = decltype(std::declval<T>() == std::declval<U>())>
|
||||
bool operator==(span<T> a, span<U> b) noexcept {
|
||||
if (a.size() != b.size()) {
|
||||
return false;
|
||||
}
|
||||
for (std::size_t i = 0; i < a.size(); ++i) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// \brief Constructs an object of type T and wraps it in a span.
|
||||
/// Before C++17, template argument deduction for class templates was not supported.
|
||||
/// \see https://en.cppreference.com/w/cpp/language/template_argument_deduction
|
||||
|
||||
template <typename T>
|
||||
auto make_span(T *arr, std::size_t count) noexcept -> span<T> {
|
||||
return {arr, count};
|
||||
}
|
||||
|
||||
template <typename T, std::size_t E>
|
||||
auto make_span(T (&arr)[E]) noexcept -> span<T> {
|
||||
return {arr};
|
||||
}
|
||||
|
||||
template <typename T, std::size_t E>
|
||||
auto make_span(std::array<T, E> &arr) noexcept -> span<T> {
|
||||
return {arr};
|
||||
}
|
||||
|
||||
template <typename T, std::size_t E>
|
||||
auto make_span(std::array<T, E> const &arr) noexcept -> span<typename std::add_const<T>::type> {
|
||||
return {arr};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto make_span(std::vector<T> &arr) noexcept -> span<T> {
|
||||
return {arr.data(), arr.size()};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto make_span(std::vector<T> const &arr) noexcept -> span<typename std::add_const<T>::type> {
|
||||
return {arr.data(), arr.size()};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto make_span(std::initializer_list<T> list) noexcept -> span<typename std::add_const<T>::type> {
|
||||
return {list.begin(), list.end()};
|
||||
}
|
||||
|
||||
inline auto make_span(std::string &str) noexcept -> span<char> {
|
||||
return {const_cast<char *>(str.data()), str.size()};
|
||||
}
|
||||
|
||||
inline auto make_span(std::string const &str) noexcept -> span<char const> {
|
||||
return {str.data(), str.size()};
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
33
include/libipc/imp/system.h
Normal file
33
include/libipc/imp/system.h
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* \file libipc/system.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Isolation and encapsulation of system APIs.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <ostream> // std::ostream
|
||||
#include <cstdint>
|
||||
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/imp/error.h"
|
||||
#include "libipc/imp/result.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace sys {
|
||||
|
||||
/// \brief A platform-dependent error code.
|
||||
LIBIPC_EXPORT std::error_code error() noexcept;
|
||||
|
||||
/// \enum The name of the `conf()` argument used to inquire about its value.
|
||||
/// \brief Certain options are supported,
|
||||
/// or what the value is of certain configurable constants or limits.
|
||||
enum class info : std::int32_t {
|
||||
page_size,
|
||||
};
|
||||
|
||||
/// \brief Get system configuration information at run time.
|
||||
LIBIPC_EXPORT result<std::int64_t> conf(info) noexcept;
|
||||
|
||||
} // namespace sys
|
||||
} // namespace ipc
|
||||
163
include/libipc/imp/uninitialized.h
Normal file
163
include/libipc/imp/uninitialized.h
Normal file
@ -0,0 +1,163 @@
|
||||
/**
|
||||
* \file libipc/uninitialized.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Uninitialized memory algorithms.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <new> // placement-new
|
||||
#include <type_traits> // std::enable_if_t
|
||||
#include <utility> // std::forward
|
||||
#include <memory> // std::construct_at, std::destroy_at, std::addressof
|
||||
#include <cstddef>
|
||||
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/imp/generic.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \brief Creates an object at a given address, like 'construct_at' in c++20
|
||||
* \see https://en.cppreference.com/w/cpp/memory/construct_at
|
||||
*/
|
||||
|
||||
// Overload for zero arguments - use value initialization
|
||||
template <typename T>
|
||||
T* construct(void *p) {
|
||||
#if defined(LIBIPC_CPP_20)
|
||||
return std::construct_at(static_cast<T *>(p));
|
||||
#else
|
||||
return ::new (p) T();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Overload for one or more arguments - prefer direct initialization
|
||||
template <typename T, typename A1, typename... A>
|
||||
auto construct(void *p, A1 &&arg1, A &&...args)
|
||||
-> std::enable_if_t<::std::is_constructible<T, A1, A...>::value, T *> {
|
||||
#if defined(LIBIPC_CPP_20)
|
||||
return std::construct_at(static_cast<T *>(p), std::forward<A1>(arg1), std::forward<A>(args)...);
|
||||
#else
|
||||
return ::new (p) T(std::forward<A1>(arg1), std::forward<A>(args)...);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Overload for non-constructible types - use aggregate initialization
|
||||
template <typename T, typename A1, typename... A>
|
||||
auto construct(void *p, A1 &&arg1, A &&...args)
|
||||
-> std::enable_if_t<!::std::is_constructible<T, A1, A...>::value, T *> {
|
||||
return ::new (p) T{std::forward<A1>(arg1), std::forward<A>(args)...};
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Destroys an object at a given address, like 'destroy_at' in c++17
|
||||
* \see https://en.cppreference.com/w/cpp/memory/destroy_at
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
void *destroy(T *p) noexcept {
|
||||
if (p == nullptr) return nullptr;
|
||||
#if defined(LIBIPC_CPP_17)
|
||||
std::destroy_at(p);
|
||||
#else
|
||||
p->~T();
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void *destroy<void>(void *p) noexcept {
|
||||
return p;
|
||||
}
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
void *destroy(T (*p)[N]) noexcept {
|
||||
if (p == nullptr) return nullptr;
|
||||
#if defined(LIBIPC_CPP_20)
|
||||
std::destroy_at(p);
|
||||
#elif defined(LIBIPC_CPP_17)
|
||||
std::destroy(std::begin(*p), std::end(*p));
|
||||
#else
|
||||
for (auto &elem : *p) destroy(std::addressof(elem));
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Destroys a range of objects.
|
||||
* \see https://en.cppreference.com/w/cpp/memory/destroy
|
||||
*/
|
||||
template <typename ForwardIt>
|
||||
void destroy(ForwardIt first, ForwardIt last) noexcept {
|
||||
#if defined(LIBIPC_CPP_17)
|
||||
std::destroy(first, last);
|
||||
#else
|
||||
for (; first != last; ++first) {
|
||||
destroy(std::addressof(*first));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Destroys a number of objects in a range.
|
||||
* \see https://en.cppreference.com/w/cpp/memory/destroy_n
|
||||
*/
|
||||
template <typename ForwardIt, typename Size>
|
||||
ForwardIt destroy_n(ForwardIt first, Size n) noexcept {
|
||||
#if defined(LIBIPC_CPP_17)
|
||||
return std::destroy_n(first, n);
|
||||
#else
|
||||
for (; n > 0; (void) ++first, --n)
|
||||
destroy(std::addressof(*first));
|
||||
return first;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Constructs objects by default-initialization
|
||||
* in an uninitialized area of memory, defined by a start and a count.
|
||||
* \see https://en.cppreference.com/w/cpp/memory/uninitialized_default_construct_n
|
||||
*/
|
||||
template <typename ForwardIt, typename Size>
|
||||
ForwardIt uninitialized_default_construct_n(ForwardIt first, Size n) {
|
||||
#if defined(LIBIPC_CPP_17)
|
||||
return std::uninitialized_default_construct_n(first, n);
|
||||
#else
|
||||
using T = typename std::iterator_traits<ForwardIt>::value_type;
|
||||
ForwardIt current = first;
|
||||
LIBIPC_TRY {
|
||||
for (; n > 0; (void) ++current, --n)
|
||||
::new (horrible_cast<void *>(std::addressof(*current))) T;
|
||||
return current;
|
||||
} LIBIPC_CATCH(...) {
|
||||
destroy(first, current);
|
||||
LIBIPC_THROW(, first);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Moves a number of objects to an uninitialized area of memory.
|
||||
* \see https://en.cppreference.com/w/cpp/memory/uninitialized_move_n
|
||||
*/
|
||||
template <typename InputIt, typename Size, typename NoThrowForwardIt>
|
||||
auto uninitialized_move_n(InputIt first, Size count, NoThrowForwardIt d_first)
|
||||
-> std::pair<InputIt, NoThrowForwardIt> {
|
||||
#if defined(LIBIPC_CPP_17)
|
||||
return std::uninitialized_move_n(first, count, d_first);
|
||||
#else
|
||||
using Value = typename std::iterator_traits<NoThrowForwardIt>::value_type;
|
||||
NoThrowForwardIt current = d_first;
|
||||
LIBIPC_TRY {
|
||||
for (; count > 0; ++first, (void) ++current, --count) {
|
||||
::new (static_cast<void *>(std::addressof(*current))) Value(std::move(*first));
|
||||
}
|
||||
} LIBIPC_CATCH(...) {
|
||||
destroy(d_first, current);
|
||||
LIBIPC_THROW(, {first, d_first});
|
||||
}
|
||||
return {first, current};
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "libipc/export.h"
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/buffer.h"
|
||||
#include "libipc/shm.h"
|
||||
@ -18,7 +18,7 @@ enum : unsigned {
|
||||
};
|
||||
|
||||
template <typename Flag>
|
||||
struct IPC_EXPORT chan_impl {
|
||||
struct LIBIPC_EXPORT chan_impl {
|
||||
static ipc::handle_t init_first();
|
||||
|
||||
static bool connect (ipc::handle_t * ph, char const * name, unsigned mode);
|
||||
|
||||
121
include/libipc/mem/block_pool.h
Normal file
121
include/libipc/mem/block_pool.h
Normal file
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* \file libipc/block_pool.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief The fixed-length memory block pool.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "libipc/mem/central_cache_pool.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
/**
|
||||
* \brief Fixed-length memory block pool.
|
||||
* \tparam BlockSize specifies the memory block size
|
||||
* \tparam BlockPoolExpansion specifies the default number of blocks to expand when the block pool is exhausted
|
||||
*/
|
||||
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
|
||||
class block_pool;
|
||||
|
||||
/// \brief General-purpose block pool for any size of memory block.
|
||||
/// \note This block pool can only be used to deallocate a group of memory blocks of unknown but consistent size,
|
||||
/// and cannot be used for memory block allocation.
|
||||
template <>
|
||||
class block_pool<0, 0> {
|
||||
|
||||
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
|
||||
friend class block_pool;
|
||||
|
||||
/// \brief The block type.
|
||||
struct block_t {
|
||||
block_t *next;
|
||||
};
|
||||
|
||||
/// \brief The central cache pool type.
|
||||
using central_cache_pool_t = central_cache_pool<block_t, 0>;
|
||||
|
||||
public:
|
||||
static constexpr std::size_t block_size = 0;
|
||||
|
||||
block_pool() noexcept : cursor_(central_cache_pool_t::instance().aqueire()) {}
|
||||
~block_pool() noexcept {
|
||||
central_cache_pool_t::instance().release(cursor_);
|
||||
}
|
||||
|
||||
block_pool(block_pool const &) = delete;
|
||||
block_pool& operator=(block_pool const &) = delete;
|
||||
|
||||
block_pool(block_pool &&rhs) noexcept : cursor_(std::exchange(rhs.cursor_, nullptr)) {}
|
||||
block_pool &operator=(block_pool &&) noexcept = delete;
|
||||
|
||||
void deallocate(void *p) noexcept {
|
||||
if (p == nullptr) return;
|
||||
block_t *b = static_cast<block_t *>(p);
|
||||
b->next = cursor_;
|
||||
cursor_ = b;
|
||||
}
|
||||
|
||||
private:
|
||||
block_t *cursor_;
|
||||
};
|
||||
|
||||
/// \brief A block pool for a block of memory of a specific size.
|
||||
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
|
||||
class block_pool {
|
||||
|
||||
/// \brief The block type.
|
||||
using block_t = block<BlockSize>;
|
||||
/// \brief The central cache pool type.
|
||||
using central_cache_pool_t = central_cache_pool<block_t, BlockPoolExpansion>;
|
||||
|
||||
/// \brief Expand the block pool when it is exhausted.
|
||||
block_t *expand() noexcept {
|
||||
return central_cache_pool_t::instance().aqueire();
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr std::size_t block_size = BlockSize;
|
||||
|
||||
block_pool() noexcept : cursor_(expand()) {}
|
||||
~block_pool() noexcept {
|
||||
central_cache_pool_t::instance().release(cursor_);
|
||||
}
|
||||
|
||||
block_pool(block_pool const &) = delete;
|
||||
block_pool& operator=(block_pool const &) = delete;
|
||||
|
||||
block_pool(block_pool &&rhs) noexcept
|
||||
: cursor_(std::exchange(rhs.cursor_, nullptr)) {}
|
||||
block_pool &operator=(block_pool &&) noexcept = delete;
|
||||
|
||||
/// \brief Used to take all memory blocks from within a general-purpose block pool.
|
||||
/// \note Of course, the actual memory blocks they manage must be the same size.
|
||||
block_pool(block_pool<0, 0> &&rhs) noexcept
|
||||
: cursor_(reinterpret_cast<block_t *>(std::exchange(rhs.cursor_, nullptr))) {}
|
||||
|
||||
void *allocate() noexcept {
|
||||
if (cursor_ == nullptr) {
|
||||
cursor_ = expand();
|
||||
if (cursor_ == nullptr) return nullptr;
|
||||
}
|
||||
block_t *p = cursor_;
|
||||
cursor_ = cursor_->next;
|
||||
return p->storage.data();
|
||||
}
|
||||
|
||||
void deallocate(void *p) noexcept {
|
||||
if (p == nullptr) return;
|
||||
block_t *b = static_cast<block_t *>(p);
|
||||
b->next = cursor_;
|
||||
cursor_ = b;
|
||||
}
|
||||
|
||||
private:
|
||||
block_t *cursor_;
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
163
include/libipc/mem/bytes_allocator.h
Normal file
163
include/libipc/mem/bytes_allocator.h
Normal file
@ -0,0 +1,163 @@
|
||||
/**
|
||||
* \file libipc/bytes_allocator.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief A generic polymorphic memory allocator.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <array>
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <utility> // std::forward
|
||||
#include <tuple> // std::ignore
|
||||
#include <cstddef>
|
||||
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/imp/uninitialized.h"
|
||||
#include "libipc/imp/byte.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
/// \brief Helper trait for memory resource.
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct has_allocate : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_allocate<T,
|
||||
typename std::enable_if<std::is_convertible<
|
||||
decltype(std::declval<T &>().allocate(std::declval<std::size_t>(),
|
||||
std::declval<std::size_t>())), void *
|
||||
>::value>::type> : std::true_type {};
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct has_deallocate : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_deallocate<T,
|
||||
decltype(std::declval<T &>().deallocate(std::declval<void *>(),
|
||||
std::declval<std::size_t>(),
|
||||
std::declval<std::size_t>()))
|
||||
> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
using is_memory_resource =
|
||||
std::enable_if_t<has_allocate <T>::value &&
|
||||
has_deallocate<T>::value, bool>;
|
||||
|
||||
/**
|
||||
* \brief An allocator which exhibits different allocation behavior
|
||||
* depending upon the memory resource from which it is constructed.
|
||||
*
|
||||
* \note Unlike `std::pmr::container_allocator`, it does not
|
||||
* rely on a specific inheritance relationship and only restricts
|
||||
* the interface behavior of the incoming memory resource object to
|
||||
* conform to `std::pmr::memory_resource`.
|
||||
*
|
||||
* \see https://en.cppreference.com/w/cpp/memory/memory_resource
|
||||
* https://en.cppreference.com/w/cpp/memory/container_allocator
|
||||
*/
|
||||
class LIBIPC_EXPORT bytes_allocator {
|
||||
|
||||
class holder_mr_base {
|
||||
public:
|
||||
virtual ~holder_mr_base() noexcept = default;
|
||||
virtual void *alloc(std::size_t, std::size_t) const = 0;
|
||||
virtual void dealloc(void *, std::size_t, std::size_t) const = 0;
|
||||
};
|
||||
|
||||
template <typename MR, typename = bool>
|
||||
class holder_mr;
|
||||
|
||||
/**
|
||||
* \brief An empty holding class used to calculate a reasonable memory size for the holder.
|
||||
* \tparam MR cannot be converted to the type of memory resource
|
||||
*/
|
||||
template <typename MR, typename U>
|
||||
class holder_mr : public holder_mr_base {
|
||||
protected:
|
||||
MR *res_;
|
||||
|
||||
public:
|
||||
holder_mr(MR *p_mr) noexcept
|
||||
: res_(p_mr) {}
|
||||
|
||||
// [MSVC] error C2259: 'bytes_allocator::holder_mr<void *,bool>': cannot instantiate abstract class.
|
||||
void *alloc(std::size_t s, std::size_t a) const override { return nullptr; }
|
||||
void dealloc(void *p, std::size_t s, std::size_t a) const override {}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief A memory resource pointer holder class for type erasure.
|
||||
* \tparam MR memory resource type
|
||||
*/
|
||||
template <typename MR>
|
||||
class holder_mr<MR, is_memory_resource<MR>> : public holder_mr<MR, void> {
|
||||
using base_t = holder_mr<MR, void>;
|
||||
|
||||
public:
|
||||
holder_mr(MR *p_mr) noexcept
|
||||
: base_t{p_mr} {}
|
||||
|
||||
void *alloc(std::size_t s, std::size_t a) const override {
|
||||
return base_t::res_->allocate(s, a);
|
||||
}
|
||||
|
||||
void dealloc(void *p, std::size_t s, std::size_t a) const override {
|
||||
base_t::res_->deallocate(p, s, a);
|
||||
}
|
||||
};
|
||||
|
||||
using void_holder_t = holder_mr<void *>;
|
||||
alignas(void_holder_t) std::array<ipc::byte, sizeof(void_holder_t)> holder_;
|
||||
|
||||
holder_mr_base & get_holder() noexcept;
|
||||
holder_mr_base const &get_holder() const noexcept;
|
||||
|
||||
void init_default_resource() noexcept;
|
||||
|
||||
public:
|
||||
/// \brief Constructs an `bytes_allocator` using the return value of
|
||||
/// `new_delete_resource::get()` as the underlying memory resource.
|
||||
bytes_allocator() noexcept;
|
||||
~bytes_allocator() noexcept;
|
||||
|
||||
bytes_allocator(bytes_allocator const &other) noexcept = default;
|
||||
bytes_allocator &operator=(bytes_allocator const &other) & noexcept = default;
|
||||
|
||||
bytes_allocator(bytes_allocator &&other) noexcept = default;
|
||||
bytes_allocator &operator=(bytes_allocator &&other) & noexcept = default;
|
||||
|
||||
/// \brief Constructs a `bytes_allocator` from a memory resource pointer.
|
||||
/// \note The lifetime of the pointer must be longer than that of bytes_allocator.
|
||||
template <typename T, is_memory_resource<T> = true>
|
||||
bytes_allocator(T *p_mr) noexcept {
|
||||
if (p_mr == nullptr) {
|
||||
init_default_resource();
|
||||
return;
|
||||
}
|
||||
std::ignore = ipc::construct<holder_mr<T>>(holder_.data(), p_mr);
|
||||
}
|
||||
|
||||
void swap(bytes_allocator &other) noexcept;
|
||||
|
||||
/// \brief Allocate/deallocate memory.
|
||||
void *allocate(std::size_t s, std::size_t = alignof(std::max_align_t)) const;
|
||||
void deallocate(void *p, std::size_t s, std::size_t = alignof(std::max_align_t)) const;
|
||||
|
||||
/// \brief Allocates uninitialized memory and constructs an object of type T in the memory.
|
||||
template <typename T, typename... A>
|
||||
T *construct(A &&...args) const {
|
||||
return ipc::construct<T>(allocate(sizeof(T), alignof(T)), std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
/// \brief Calls the destructor of the object pointed to by p and deallocates the memory.
|
||||
template <typename T>
|
||||
void destroy(T *p) const noexcept {
|
||||
deallocate(ipc::destroy(p), sizeof(T), alignof(T));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
20
include/libipc/mem/central_cache_allocator.h
Normal file
20
include/libipc/mem/central_cache_allocator.h
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* \file libipc/central_cache_allocator.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief The central cache allocator getter.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/mem/bytes_allocator.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
/// \brief Get the central cache allocator.
|
||||
/// \note The central cache allocator is used to allocate memory for the central cache pool.
|
||||
/// The underlying memory resource is a `monotonic_buffer_resource` with a fixed-size buffer.
|
||||
LIBIPC_EXPORT bytes_allocator ¢ral_cache_allocator() noexcept;
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
132
include/libipc/mem/central_cache_pool.h
Normal file
132
include/libipc/mem/central_cache_pool.h
Normal file
@ -0,0 +1,132 @@
|
||||
/**
|
||||
* \file libipc/central_cache_pool.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief The fixed-length memory block central cache pool.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <array>
|
||||
|
||||
#include "libipc/imp/byte.h"
|
||||
#include "libipc/concur/intrusive_stack.h"
|
||||
#include "libipc/mem/central_cache_allocator.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
/**
|
||||
* \brief The block type.
|
||||
* \tparam BlockSize specifies the memory block size
|
||||
*/
|
||||
template <std::size_t BlockSize>
|
||||
union block {
|
||||
block *next;
|
||||
alignas(std::max_align_t) std::array<byte, BlockSize> storage;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief A fixed-length memory block central cache pool.
|
||||
* \tparam BlockT specifies the memory block type
|
||||
*/
|
||||
template <typename BlockT, std::size_t BlockPoolExpansion>
|
||||
class central_cache_pool {
|
||||
|
||||
/// \brief The block type, which should be a union of a pointer and a storage.
|
||||
using block_t = BlockT;
|
||||
/// \brief The chunk type, which is an array of blocks.
|
||||
using chunk_t = std::array<block_t, BlockPoolExpansion>;
|
||||
/// \brief The node type, which is used to store the block pointer.
|
||||
using node_t = typename concur::intrusive_stack<block_t *>::node;
|
||||
|
||||
/// \brief The central cache stack.
|
||||
concur::intrusive_stack<block_t *> cached_;
|
||||
concur::intrusive_stack<block_t *> aqueired_;
|
||||
|
||||
central_cache_pool() noexcept = default;
|
||||
|
||||
public:
|
||||
block_t *aqueire() noexcept {
|
||||
auto *n = cached_.pop();
|
||||
if (n != nullptr) {
|
||||
aqueired_.push(n);
|
||||
return n->value;
|
||||
}
|
||||
auto *chunk = central_cache_allocator().construct<chunk_t>();
|
||||
if (chunk == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
for (std::size_t i = 0; i < BlockPoolExpansion - 1; ++i) {
|
||||
(*chunk)[i].next = &(*chunk)[i + 1];
|
||||
}
|
||||
chunk->back().next = nullptr;
|
||||
return chunk->data();
|
||||
}
|
||||
|
||||
void release(block_t *p) noexcept {
|
||||
if (p == nullptr) return;
|
||||
auto *a = aqueired_.pop();
|
||||
if (a == nullptr) {
|
||||
a = central_cache_allocator().construct<node_t>();
|
||||
if (a == nullptr) return;
|
||||
}
|
||||
a->value = p;
|
||||
cached_.push(a);
|
||||
}
|
||||
|
||||
/// \brief Get the singleton instance.
|
||||
static central_cache_pool &instance() noexcept {
|
||||
static central_cache_pool pool;
|
||||
return pool;
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief A fixed-length memory block central cache pool with no default expansion size.
|
||||
template <typename BlockT>
|
||||
class central_cache_pool<BlockT, 0> {
|
||||
|
||||
/// \brief The block type, which should be a union of a pointer and a storage.
|
||||
using block_t = BlockT;
|
||||
/// \brief The node type, which is used to store the block pointer.
|
||||
using node_t = typename concur::intrusive_stack<block_t *>::node;
|
||||
|
||||
/// \brief The central cache stack.
|
||||
concur::intrusive_stack<block_t *> cached_;
|
||||
concur::intrusive_stack<block_t *> aqueired_;
|
||||
|
||||
central_cache_pool() noexcept = default;
|
||||
|
||||
public:
|
||||
block_t *aqueire() noexcept {
|
||||
auto *n = cached_.pop();
|
||||
if (n != nullptr) {
|
||||
aqueired_.push(n);
|
||||
return n->value;
|
||||
}
|
||||
// For pools with no default expansion size,
|
||||
// the central cache pool is only buffered, not allocated.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void release(block_t *p) noexcept {
|
||||
if (p == nullptr) return;
|
||||
auto *a = aqueired_.pop();
|
||||
if (a == nullptr) {
|
||||
a = central_cache_allocator().construct<node_t>();
|
||||
if (a == nullptr) return;
|
||||
}
|
||||
a->value = p;
|
||||
cached_.push(a);
|
||||
}
|
||||
|
||||
/// \brief Get the singleton instance.
|
||||
static central_cache_pool &instance() noexcept {
|
||||
static central_cache_pool pool;
|
||||
return pool;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
101
include/libipc/mem/container_allocator.h
Normal file
101
include/libipc/mem/container_allocator.h
Normal file
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* \file libipc/container_allocator.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief An allocator that can be used by all standard library containers.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <limits>
|
||||
|
||||
#include "libipc/imp/uninitialized.h"
|
||||
#include "libipc/mem/new.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
/**
|
||||
* \brief An allocator that can be used by all standard library containers.
|
||||
*
|
||||
* \see https://en.cppreference.com/w/cpp/memory/allocator
|
||||
* https://en.cppreference.com/w/cpp/memory/polymorphic_allocator
|
||||
*/
|
||||
template <typename T>
|
||||
class container_allocator {
|
||||
|
||||
template <typename U>
|
||||
friend class container_allocator;
|
||||
|
||||
public:
|
||||
// type definitions
|
||||
typedef T value_type;
|
||||
typedef value_type * pointer;
|
||||
typedef const value_type *const_pointer;
|
||||
typedef value_type & reference;
|
||||
typedef const value_type &const_reference;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
|
||||
// the other type of std_allocator
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other = container_allocator<U>;
|
||||
};
|
||||
|
||||
container_allocator() noexcept {}
|
||||
|
||||
// construct by copying (do nothing)
|
||||
container_allocator (container_allocator<T> const &) noexcept {}
|
||||
container_allocator& operator=(container_allocator<T> const &) noexcept { return *this; }
|
||||
|
||||
// construct from a related allocator (do nothing)
|
||||
template <typename U> container_allocator (container_allocator<U> const &) noexcept {}
|
||||
template <typename U> container_allocator &operator=(container_allocator<U> const &) noexcept { return *this; }
|
||||
|
||||
container_allocator (container_allocator &&) noexcept = default;
|
||||
container_allocator& operator=(container_allocator &&) noexcept = default;
|
||||
|
||||
constexpr size_type max_size() const noexcept {
|
||||
return (std::numeric_limits<size_type>::max)() / sizeof(value_type);
|
||||
}
|
||||
|
||||
pointer allocate(size_type count) noexcept {
|
||||
if (count == 0) return nullptr;
|
||||
if (count > this->max_size()) return nullptr;
|
||||
// Allocate raw memory without constructing objects
|
||||
// Construction should be done by construct() member function
|
||||
void *p = mem::alloc(sizeof(value_type) * count);
|
||||
return static_cast<pointer>(p);
|
||||
}
|
||||
|
||||
void deallocate(pointer p, size_type count) noexcept {
|
||||
if (count == 0) return;
|
||||
if (count > this->max_size()) return;
|
||||
// Deallocate raw memory without destroying objects
|
||||
// Destruction should be done by destroy() member function before deallocate
|
||||
mem::free(p, sizeof(value_type) * count);
|
||||
}
|
||||
|
||||
template <typename... P>
|
||||
static void construct(pointer p, P && ... params) {
|
||||
std::ignore = ipc::construct<T>(p, std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
static void destroy(pointer p) {
|
||||
std::ignore = ipc::destroy(p);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename U>
|
||||
constexpr bool operator==(container_allocator<T> const &, container_allocator<U> const &) noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
constexpr bool operator!=(container_allocator<T> const &, container_allocator<U> const &) noexcept {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
83
include/libipc/mem/memory_resource.h
Normal file
83
include/libipc/mem/memory_resource.h
Normal file
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* \file libipc/memory_resource.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Implement memory allocation strategies that can be used by ipc::mem::bytes_allocator.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <cstddef> // std::size_t, std::max_align_t
|
||||
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/imp/span.h"
|
||||
#include "libipc/imp/byte.h"
|
||||
#include "libipc/mem/bytes_allocator.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
/**
|
||||
* \class LIBIPC_EXPORT new_delete_resource
|
||||
* \brief A memory resource that uses the
|
||||
* standard memory allocation and deallocation interface to allocate memory.
|
||||
* \see https://en.cppreference.com/w/cpp/memory/new_delete_resource
|
||||
*/
|
||||
class LIBIPC_EXPORT new_delete_resource {
|
||||
public:
|
||||
/// \brief Returns a pointer to a `new_delete_resource`.
|
||||
static new_delete_resource *get() noexcept;
|
||||
|
||||
/// \brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment.
|
||||
/// \remark Returns nullptr if storage of the requested size and alignment cannot be obtained.
|
||||
/// \see https://en.cppreference.com/w/cpp/memory/memory_resource/do_allocate
|
||||
void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
|
||||
|
||||
/// \brief Deallocates the storage pointed to by p.
|
||||
/// \see https://en.cppreference.com/w/cpp/memory/memory_resource/deallocate
|
||||
void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
|
||||
};
|
||||
|
||||
/**
|
||||
* \class LIBIPC_EXPORT monotonic_buffer_resource
|
||||
* \brief A special-purpose memory resource class
|
||||
* that releases the allocated memory only when the resource is destroyed.
|
||||
* \see https://en.cppreference.com/w/cpp/memory/monotonic_buffer_resource
|
||||
*/
|
||||
class LIBIPC_EXPORT monotonic_buffer_resource {
|
||||
|
||||
bytes_allocator upstream_;
|
||||
|
||||
struct node {
|
||||
node *next;
|
||||
std::size_t size;
|
||||
} *free_list_;
|
||||
|
||||
ipc::byte * head_;
|
||||
ipc::byte * tail_;
|
||||
std::size_t next_size_;
|
||||
|
||||
ipc::byte * const initial_buffer_;
|
||||
std::size_t const initial_size_;
|
||||
|
||||
public:
|
||||
monotonic_buffer_resource() noexcept;
|
||||
explicit monotonic_buffer_resource(bytes_allocator upstream) noexcept;
|
||||
explicit monotonic_buffer_resource(std::size_t initial_size) noexcept;
|
||||
monotonic_buffer_resource(std::size_t initial_size, bytes_allocator upstream) noexcept;
|
||||
monotonic_buffer_resource(ipc::span<ipc::byte> buffer) noexcept;
|
||||
monotonic_buffer_resource(ipc::span<ipc::byte> buffer, bytes_allocator upstream) noexcept;
|
||||
|
||||
~monotonic_buffer_resource() noexcept;
|
||||
|
||||
monotonic_buffer_resource(monotonic_buffer_resource const &) = delete;
|
||||
monotonic_buffer_resource &operator=(monotonic_buffer_resource const &) = delete;
|
||||
|
||||
bytes_allocator upstream_resource() const noexcept;
|
||||
void release() noexcept;
|
||||
|
||||
void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
|
||||
void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
117
include/libipc/mem/new.h
Normal file
117
include/libipc/mem/new.h
Normal file
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* \file libipc/mem.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief Global memory management.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
#include <tuple>
|
||||
|
||||
#include "libipc/imp/aligned.h"
|
||||
#include "libipc/imp/uninitialized.h"
|
||||
#include "libipc/imp/byte.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/mem/memory_resource.h"
|
||||
#include "libipc/mem/block_pool.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
/// \brief Defines the memory block collector interface.
|
||||
class LIBIPC_EXPORT block_collector {
|
||||
public:
|
||||
virtual ~block_collector() noexcept = default;
|
||||
virtual void *allocate(std::size_t /*bytes*/) noexcept = 0;
|
||||
virtual void deallocate(void */*p*/, std::size_t /*bytes*/) noexcept = 0;
|
||||
};
|
||||
|
||||
/// \brief Matches the appropriate memory block resource based on a specified size.
|
||||
LIBIPC_EXPORT block_collector &get_regular_resource(std::size_t s) noexcept;
|
||||
|
||||
/// \brief Allocates storage with a size of at least bytes bytes.
|
||||
LIBIPC_EXPORT void *alloc(std::size_t bytes) noexcept;
|
||||
LIBIPC_EXPORT void free (void *p, std::size_t bytes) noexcept;
|
||||
|
||||
namespace detail_new {
|
||||
|
||||
#if defined(LIBIPC_CPP_17)
|
||||
using recycle_t = void (*)(void *p) noexcept;
|
||||
#else
|
||||
using recycle_t = void (*)(void *p);
|
||||
#endif
|
||||
|
||||
static constexpr std::size_t recycler_size = round_up(sizeof(recycle_t), alignof(std::size_t));
|
||||
static constexpr std::size_t allocated_size = sizeof(std::size_t);
|
||||
static constexpr std::size_t regular_head_size = round_up(recycler_size + allocated_size, alignof(std::max_align_t));
|
||||
|
||||
template <typename T>
|
||||
struct do_allocate {
|
||||
template <typename... A>
|
||||
static T *apply(A &&... args) noexcept {
|
||||
void *b = mem::alloc(regular_head_size + sizeof(T));
|
||||
auto *p = static_cast<byte *>(b) + regular_head_size;
|
||||
LIBIPC_TRY {
|
||||
T *t = construct<T>(p, std::forward<A>(args)...);
|
||||
*reinterpret_cast<recycle_t *>(b)
|
||||
= [](void *p) noexcept {
|
||||
mem::free(static_cast<byte *>(destroy(static_cast<T *>(p))) - regular_head_size
|
||||
, regular_head_size + sizeof(T));
|
||||
};
|
||||
return t;
|
||||
} LIBIPC_CATCH(...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct do_allocate<void> {
|
||||
static void *apply(std::size_t bytes) noexcept {
|
||||
if (bytes == 0) return nullptr;
|
||||
std::size_t rbz = regular_head_size + bytes;
|
||||
void *b = mem::alloc(rbz);
|
||||
*reinterpret_cast<recycle_t *>(b)
|
||||
= [](void *p) noexcept {
|
||||
auto *b = static_cast<byte *>(p) - regular_head_size;
|
||||
mem::free(b, *reinterpret_cast<std::size_t *>(b + recycler_size));
|
||||
};
|
||||
auto *z = static_cast<byte *>(b) + recycler_size;
|
||||
*reinterpret_cast<std::size_t *>(z) = rbz;
|
||||
return static_cast<byte *>(b) + regular_head_size;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail_new
|
||||
|
||||
/// \brief Creates an object based on the specified type and parameters with block pool resource.
|
||||
/// \note This function is thread-safe.
|
||||
template <typename T, typename... A>
|
||||
T *$new(A &&... args) noexcept {
|
||||
return detail_new::do_allocate<T>::apply(std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
/// \brief Destroys object previously allocated by the `$new` and releases obtained memory area.
|
||||
/// \note This function is thread-safe. If the pointer type passed in is different from `$new`,
|
||||
/// additional performance penalties may be incurred.
|
||||
inline void $delete(void *p) noexcept {
|
||||
if (p == nullptr) return;
|
||||
auto *r = reinterpret_cast<detail_new::recycle_t *>(static_cast<byte *>(p) - detail_new::regular_head_size);
|
||||
(*r)(p);
|
||||
}
|
||||
|
||||
/// \brief The destruction policy used by std::unique_ptr.
|
||||
/// \see https://en.cppreference.com/w/cpp/memory/default_delete
|
||||
struct deleter {
|
||||
template <typename T>
|
||||
void operator()(T *p) const noexcept {
|
||||
$delete(p);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
@ -3,13 +3,13 @@
|
||||
#include <cstdint> // std::uint64_t
|
||||
#include <system_error>
|
||||
|
||||
#include "libipc/export.h"
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/def.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace sync {
|
||||
|
||||
class IPC_EXPORT mutex {
|
||||
class LIBIPC_EXPORT mutex {
|
||||
mutex(mutex const &) = delete;
|
||||
mutex &operator=(mutex const &) = delete;
|
||||
|
||||
|
||||
@ -1,103 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
#include "libipc/export.h"
|
||||
#include "libipc/def.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
class IPC_EXPORT pool_alloc {
|
||||
public:
|
||||
static void* alloc(std::size_t size) noexcept;
|
||||
static void free (void* p, std::size_t size) noexcept;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// construct/destruct an object
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
struct impl {
|
||||
template <typename... P>
|
||||
static T* construct(T* p, P&&... params) {
|
||||
::new (p) T(std::forward<P>(params)...);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void destruct(T* p) {
|
||||
reinterpret_cast<T*>(p)->~T();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, size_t N>
|
||||
struct impl<T[N]> {
|
||||
using type = T[N];
|
||||
|
||||
template <typename... P>
|
||||
static type* construct(type* p, P&&... params) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
impl<T>::construct(&((*p)[i]), std::forward<P>(params)...);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static void destruct(type* p) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
impl<T>::destruct(&((*p)[i]));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, typename... P>
|
||||
T* construct(T* p, P&&... params) {
|
||||
return detail::impl<T>::construct(p, std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
template <typename T, typename... P>
|
||||
T* construct(void* p, P&&... params) {
|
||||
return construct(static_cast<T*>(p), std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void destruct(T* p) {
|
||||
return detail::impl<T>::destruct(p);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void destruct(void* p) {
|
||||
destruct(static_cast<T*>(p));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// general alloc/free
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
inline void* alloc(std::size_t size) {
|
||||
return pool_alloc::alloc(size);
|
||||
}
|
||||
|
||||
template <typename T, typename... P>
|
||||
T* alloc(P&&... params) {
|
||||
return construct<T>(pool_alloc::alloc(sizeof(T)), std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
inline void free(void* p, std::size_t size) {
|
||||
pool_alloc::free(p, size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void free(T* p) {
|
||||
if (p == nullptr) return;
|
||||
destruct(p);
|
||||
pool_alloc::free(p, sizeof(T));
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
@ -2,13 +2,13 @@
|
||||
|
||||
#include <cstdint> // std::uint64_t
|
||||
|
||||
#include "libipc/export.h"
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/def.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace sync {
|
||||
|
||||
class IPC_EXPORT semaphore {
|
||||
class LIBIPC_EXPORT semaphore {
|
||||
semaphore(semaphore const &) = delete;
|
||||
semaphore &operator=(semaphore const &) = delete;
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "libipc/export.h"
|
||||
#include "libipc/imp/export.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace shm {
|
||||
@ -15,16 +15,16 @@ enum : unsigned {
|
||||
open = 0x02
|
||||
};
|
||||
|
||||
IPC_EXPORT id_t acquire(char const * name, std::size_t size, unsigned mode = create | open);
|
||||
IPC_EXPORT void * get_mem(id_t id, std::size_t * size);
|
||||
IPC_EXPORT std::int32_t release(id_t id) noexcept;
|
||||
IPC_EXPORT void remove (id_t id) noexcept;
|
||||
IPC_EXPORT void remove (char const * name) noexcept;
|
||||
LIBIPC_EXPORT id_t acquire(char const * name, std::size_t size, unsigned mode = create | open);
|
||||
LIBIPC_EXPORT void * get_mem(id_t id, std::size_t * size);
|
||||
LIBIPC_EXPORT std::int32_t release(id_t id) noexcept;
|
||||
LIBIPC_EXPORT void remove (id_t id) noexcept;
|
||||
LIBIPC_EXPORT void remove (char const * name) noexcept;
|
||||
|
||||
IPC_EXPORT std::int32_t get_ref(id_t id);
|
||||
IPC_EXPORT void sub_ref(id_t id);
|
||||
LIBIPC_EXPORT std::int32_t get_ref(id_t id);
|
||||
LIBIPC_EXPORT void sub_ref(id_t id);
|
||||
|
||||
class IPC_EXPORT handle {
|
||||
class LIBIPC_EXPORT handle {
|
||||
public:
|
||||
handle();
|
||||
handle(char const * name, std::size_t size, unsigned mode = create | open);
|
||||
|
||||
@ -5,6 +5,8 @@ set (PACKAGE_VERSION 1.3.0)
|
||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc SRC_FILES)
|
||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/sync SRC_FILES)
|
||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/platform SRC_FILES)
|
||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/imp SRC_FILES)
|
||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/mem SRC_FILES)
|
||||
|
||||
file(GLOB HEAD_FILES
|
||||
${LIBIPC_PROJECT_DIR}/include/libipc/*.h
|
||||
|
||||
@ -33,7 +33,7 @@ public:
|
||||
void init() {
|
||||
/* DCLP */
|
||||
if (!constructed_.load(std::memory_order_acquire)) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lc_);
|
||||
LIBIPC_UNUSED auto guard = ipc::detail::unique_lock(lc_);
|
||||
if (!constructed_.load(std::memory_order_relaxed)) {
|
||||
::new (this) conn_head_base;
|
||||
constructed_.store(true, std::memory_order_release);
|
||||
|
||||
365
src/libipc/imp/codecvt.cpp
Normal file
365
src/libipc/imp/codecvt.cpp
Normal file
@ -0,0 +1,365 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
|
||||
#include "libipc/imp/codecvt.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
|
||||
#if defined(LIBIPC_OS_WIN)
|
||||
# include "libipc/platform/win/codecvt.h"
|
||||
#endif
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \brief The transform between local-character-set(UTF-8/GBK/...) and UTF-16/32.
|
||||
*
|
||||
* Modified from UnicodeConverter.
|
||||
* Copyright (c) 2010. Jianhui Qin (http://blog.csdn.net/jhqin).
|
||||
*
|
||||
* \remarks codecvt_utf8_utf16/std::wstring_convert is deprecated.
|
||||
* \see https://codingtidbit.com/2020/02/09/c17-codecvt_utf8-is-deprecated/
|
||||
* https://stackoverflow.com/questions/42946335/deprecated-header-codecvt-replacement
|
||||
* https://en.cppreference.com/w/cpp/locale/codecvt/in
|
||||
*/
|
||||
namespace {
|
||||
|
||||
/// \brief X-bit unicode transformation format
|
||||
enum class ufmt {
|
||||
utf8,
|
||||
utf16,
|
||||
utf32,
|
||||
};
|
||||
|
||||
template <typename T, ufmt, typename = void>
|
||||
struct utf_compatible : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct utf_compatible<T, ufmt::utf8,
|
||||
std::enable_if_t<std::is_fundamental<T>::value && (sizeof(T) == 1)>> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct utf_compatible<T, ufmt::utf16,
|
||||
std::enable_if_t<std::is_fundamental<T>::value && (sizeof(T) == 2)>> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct utf_compatible<T, ufmt::utf32,
|
||||
std::enable_if_t<std::is_fundamental<T>::value && (sizeof(T) == 4)>> : std::true_type {};
|
||||
|
||||
template <typename T, ufmt Fmt>
|
||||
constexpr bool utf_compatible_v = utf_compatible<T, Fmt>::value;
|
||||
|
||||
/**
|
||||
* \brief UTF-32 --> UTF-8
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
auto cvt_char(T src, U* des, std::size_t dlen) noexcept
|
||||
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf32> &&
|
||||
utf_compatible_v<U, ufmt::utf8>, std::size_t> {
|
||||
if (src == 0) return 0;
|
||||
|
||||
constexpr std::uint8_t prefix[] = {
|
||||
0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC
|
||||
};
|
||||
constexpr std::uint32_t codeup[] = {
|
||||
0x80, // U+00000000 - U+0000007F
|
||||
0x800, // U+00000080 - U+000007FF
|
||||
0x10000, // U+00000800 - U+0000FFFF
|
||||
0x200000, // U+00010000 - U+001FFFFF
|
||||
0x4000000, // U+00200000 - U+03FFFFFF
|
||||
0x80000000 // U+04000000 - U+7FFFFFFF
|
||||
};
|
||||
|
||||
std::size_t i, len = sizeof(codeup) / sizeof(std::uint32_t);
|
||||
for(i = 0; i < len; ++i) {
|
||||
if (static_cast<std::uint32_t>(src) < codeup[i]) break;
|
||||
}
|
||||
if (i == len) return 0; // the src is invalid
|
||||
|
||||
len = i + 1;
|
||||
if (des != nullptr) {
|
||||
if (dlen > i) for (; i > 0; --i) {
|
||||
des[i] = static_cast<U>((src & 0x3F) | 0x80);
|
||||
src >>= 6;
|
||||
}
|
||||
des[0] = static_cast<U>(src | prefix[len - 1]);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief UTF-8 --> UTF-32
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
auto cvt_char(T const *src, std::size_t slen, U &des) noexcept
|
||||
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf8> &&
|
||||
utf_compatible_v<U, ufmt::utf32>, std::size_t> {
|
||||
if ((src == nullptr) || (*src) == 0) return 0;
|
||||
if (slen == 0) return 0;
|
||||
|
||||
std::uint8_t b = (std::uint8_t)*(src++);
|
||||
|
||||
if (b < 0x80) {
|
||||
des = b;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (b < 0xC0 || b > 0xFD) return 0; // the src is invalid
|
||||
|
||||
std::size_t len;
|
||||
if (b < 0xE0) {
|
||||
des = b & 0x1F;
|
||||
len = 2;
|
||||
} else if (b < 0xF0) {
|
||||
des = b & 0x0F;
|
||||
len = 3;
|
||||
} else if (b < 0xF8) {
|
||||
des = b & 0x07;
|
||||
len = 4;
|
||||
} else if (b < 0xFC) {
|
||||
des = b & 0x03;
|
||||
len = 5;
|
||||
} else {
|
||||
des = b & 0x01;
|
||||
len = 6;
|
||||
}
|
||||
|
||||
if (slen < len) return 0;
|
||||
std::size_t i = 1;
|
||||
for(; i < len; ++i) {
|
||||
b = *(src++);
|
||||
if ((b < 0x80) || (b > 0xBF)) return 0; // the src is invalid
|
||||
des = (des << 6) + (b & 0x3F);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief UTF-32 --> UTF-16
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
auto cvt_char(T src, U *des, std::size_t dlen) noexcept
|
||||
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf32> &&
|
||||
utf_compatible_v<U, ufmt::utf16>, std::size_t> {
|
||||
if (src == 0) return 0;
|
||||
|
||||
if (src <= 0xFFFF) {
|
||||
if ((des != nullptr) && (dlen != 0)) {
|
||||
(*des) = static_cast<U>(src);
|
||||
}
|
||||
return 1;
|
||||
} else if (src <= 0xEFFFF) {
|
||||
if ((des != nullptr) && (dlen > 1)) {
|
||||
des[0] = static_cast<U>(0xD800 + (src >> 10) - 0x40); // high
|
||||
des[1] = static_cast<U>(0xDC00 + (src & 0x03FF)); // low
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief UTF-16 --> UTF-32
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
auto cvt_char(T const *src, std::size_t slen, U &des)
|
||||
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf16> &&
|
||||
utf_compatible_v<U, ufmt::utf32>, std::size_t> {
|
||||
if ((src == nullptr) || (*src) == 0) return 0;
|
||||
if (slen == 0) return 0;
|
||||
|
||||
std::uint16_t w1 = src[0];
|
||||
if ((w1 >= 0xD800) && (w1 <= 0xDFFF)) {
|
||||
if (w1 < 0xDC00) {
|
||||
if (slen < 2) return 0;
|
||||
std::uint16_t w2 = src[1];
|
||||
if ((w2 >= 0xDC00) && (w2 <= 0xDFFF)) {
|
||||
des = (w2 & 0x03FF) + (((w1 & 0x03FF) + 0x40) << 10);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
return 0; // the src is invalid
|
||||
}
|
||||
des = w1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief UTF-16 --> UTF-8
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
auto cvt_char(T src, U *des, std::size_t dlen) noexcept
|
||||
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf16> &&
|
||||
utf_compatible_v<U, ufmt::utf8>, std::size_t> {
|
||||
// make utf-16 to utf-32
|
||||
std::uint32_t tmp;
|
||||
if (cvt_char(&src, 1, tmp) != 1) return 0;
|
||||
// make utf-32 to utf-8
|
||||
return cvt_char(tmp, des, dlen);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief UTF-8 --> UTF-16
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
auto cvt_char(T const *src, std::size_t slen, U &des)
|
||||
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf8> &&
|
||||
utf_compatible_v<U, ufmt::utf16>, std::size_t> {
|
||||
// make utf-8 to utf-32
|
||||
std::uint32_t tmp;
|
||||
std::size_t len = cvt_char(src, slen, tmp);
|
||||
if (len == 0) return 0;
|
||||
// make utf-32 to utf-16
|
||||
if (cvt_char(tmp, &des, 1) != 1) return 0;
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief UTF-32 string --> UTF-8/16 string
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
auto cvt_cstr_utf(T const *src, std::size_t slen, U *des, std::size_t dlen) noexcept
|
||||
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf32> &&
|
||||
(utf_compatible_v<U, ufmt::utf16> || utf_compatible_v<U, ufmt::utf8>), std::size_t> {
|
||||
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
|
||||
// source string is empty
|
||||
return 0;
|
||||
}
|
||||
std::size_t num = 0, len = 0;
|
||||
for (std::size_t i = 0; (i < slen) && ((*src) != 0); ++src, ++i) {
|
||||
len = cvt_char(*src, des, dlen);
|
||||
if (len == 0) return 0;
|
||||
if (des != nullptr) {
|
||||
des += len;
|
||||
if (dlen < len) {
|
||||
dlen = 0;
|
||||
} else {
|
||||
dlen -= len;
|
||||
}
|
||||
}
|
||||
num += len;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief UTF-8/16 string --> UTF-32 string
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
auto cvt_cstr_utf(T const *src, std::size_t slen, U *des, std::size_t dlen) noexcept
|
||||
-> std::enable_if_t<utf_compatible_v<U, ufmt::utf32> &&
|
||||
(utf_compatible_v<T, ufmt::utf16> || utf_compatible_v<T, ufmt::utf8>), std::size_t> {
|
||||
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
|
||||
// source string is empty
|
||||
return 0;
|
||||
}
|
||||
std::size_t num = 0;
|
||||
for (std::size_t i = 0; (i < slen) && ((*src) != 0);) {
|
||||
std::uint32_t tmp;
|
||||
std::size_t len = cvt_char(src, slen - i, tmp);
|
||||
if (len == 0) return 0;
|
||||
if ((des != nullptr) && (dlen > 0)) {
|
||||
(*des) = tmp;
|
||||
++des;
|
||||
dlen -= 1;
|
||||
}
|
||||
src += len;
|
||||
i += len;
|
||||
num += 1;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief UTF-8/16 string --> UTF-16/8 string
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
auto cvt_cstr_utf(T const *src, std::size_t slen, U *des, std::size_t dlen) noexcept
|
||||
-> std::enable_if_t<(utf_compatible_v<T, ufmt::utf8> && utf_compatible_v<U, ufmt::utf16>) ||
|
||||
(utf_compatible_v<T, ufmt::utf16> && utf_compatible_v<U, ufmt::utf8>), std::size_t> {
|
||||
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
|
||||
// source string is empty
|
||||
return 0;
|
||||
}
|
||||
std::size_t num = 0;
|
||||
for (std::size_t i = 0; (i < slen) && ((*src) != 0);) {
|
||||
// make utf-x to utf-32
|
||||
std::uint32_t tmp;
|
||||
std::size_t len = cvt_char(src, slen - i, tmp);
|
||||
if (len == 0) return 0;
|
||||
src += len;
|
||||
i += len;
|
||||
// make utf-32 to utf-y
|
||||
len = cvt_char(tmp, des, dlen);
|
||||
if (len == 0) return 0;
|
||||
if (des != nullptr) {
|
||||
des += len;
|
||||
if (dlen < len) {
|
||||
dlen = 0;
|
||||
} else {
|
||||
dlen -= len;
|
||||
}
|
||||
}
|
||||
num += len;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
auto cvt_cstr_utf(T const *src, std::size_t slen, U *des, std::size_t dlen) noexcept
|
||||
-> std::enable_if_t<(sizeof(T) == sizeof(U)), std::size_t> {
|
||||
if ((des == nullptr) || (dlen == 0)) {
|
||||
return slen;
|
||||
}
|
||||
std::size_t r = (std::min)(slen, dlen);
|
||||
std::memcpy(des, src, r * sizeof(T));
|
||||
return r;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#define LIBIPC_DEF_CVT_CSTR_($CHAR_T, $CHAR_U) \
|
||||
template <> \
|
||||
std::size_t cvt_cstr($CHAR_T const *src, std::size_t slen, $CHAR_U *des, std::size_t dlen) noexcept { \
|
||||
return cvt_cstr_utf(src, slen, des, dlen); \
|
||||
}
|
||||
// #define LIBIPC_DEF_CVT_CSTR_($CHAR_T, $CHAR_U)
|
||||
|
||||
LIBIPC_DEF_CVT_CSTR_(char , char)
|
||||
LIBIPC_DEF_CVT_CSTR_(char , char16_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(char , char32_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(wchar_t , wchar_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(char16_t, char16_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(char16_t, char)
|
||||
LIBIPC_DEF_CVT_CSTR_(char16_t, char32_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(char32_t, char32_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(char32_t, char)
|
||||
LIBIPC_DEF_CVT_CSTR_(char32_t, char16_t)
|
||||
#if !defined(LIBIPC_OS_WIN)
|
||||
LIBIPC_DEF_CVT_CSTR_(char , wchar_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(wchar_t , char)
|
||||
LIBIPC_DEF_CVT_CSTR_(wchar_t , char16_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(wchar_t , char32_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(char16_t, wchar_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(char32_t, wchar_t)
|
||||
#endif // !defined(LIBIPC_OS_WIN)
|
||||
|
||||
#if defined(LIBIPC_CPP_20)
|
||||
LIBIPC_DEF_CVT_CSTR_(char8_t , char8_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(char8_t , char)
|
||||
LIBIPC_DEF_CVT_CSTR_(char8_t , char16_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(char8_t , char32_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(char , char8_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(char16_t, char8_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(char32_t, char8_t)
|
||||
#if !defined(LIBIPC_OS_WIN)
|
||||
LIBIPC_DEF_CVT_CSTR_(char8_t , wchar_t)
|
||||
LIBIPC_DEF_CVT_CSTR_(wchar_t , char8_t)
|
||||
#endif // !defined(LIBIPC_OS_WIN)
|
||||
#endif // defined(LIBIPC_CPP_20)
|
||||
|
||||
#undef LIBIPC_DEF_CVT_CSTR_
|
||||
|
||||
} // namespace ipc
|
||||
329
src/libipc/imp/fmt.cpp
Normal file
329
src/libipc/imp/fmt.cpp
Normal file
@ -0,0 +1,329 @@
|
||||
|
||||
#include <cstdio> // std::snprintf
|
||||
#include <iomanip> // std::put_time
|
||||
#include <sstream> // std::ostringstream
|
||||
#include <array>
|
||||
#include <cstring> // std::memcpy
|
||||
#include <algorithm> // std::min
|
||||
#include <initializer_list>
|
||||
#include <cstdint>
|
||||
|
||||
#include "libipc/imp/fmt.h"
|
||||
#include "libipc/imp/codecvt.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \brief Format conversions helpers.
|
||||
* \see http://personal.ee.surrey.ac.uk/Personal/R.Bowden/C/printf.html
|
||||
* https://en.cppreference.com/w/cpp/io/c/fprintf
|
||||
*/
|
||||
namespace {
|
||||
|
||||
struct sfmt_policy {
|
||||
static constexpr std::size_t aligned_size = 32U;
|
||||
};
|
||||
|
||||
template <typename Policy = sfmt_policy>
|
||||
span<char> local_fmt_sbuf() noexcept {
|
||||
thread_local std::array<char, Policy::aligned_size> sbuf;
|
||||
return sbuf;
|
||||
}
|
||||
|
||||
span<char const> normalize(span<char const> const &a) {
|
||||
if (a.empty()) return {};
|
||||
return a.first(a.size() - (a.back() == '\0' ? 1 : 0));
|
||||
}
|
||||
|
||||
span<char> smem_cpy(span<char> const &sbuf, span<char const> a) noexcept {
|
||||
if (sbuf.empty()) return {};
|
||||
a = normalize(a);
|
||||
auto sz = (std::min)(sbuf.size() - 1, a.size());
|
||||
if (sz != 0) std::memcpy(sbuf.data(), a.data(), sz);
|
||||
return sbuf.first(sz);
|
||||
}
|
||||
|
||||
span<char> sbuf_cpy(span<char> sbuf, span<char const> const &a) noexcept {
|
||||
sbuf = smem_cpy(sbuf, a);
|
||||
*sbuf.end() = '\0';
|
||||
return sbuf;
|
||||
}
|
||||
|
||||
span<char> sbuf_cat(span<char> const &sbuf, std::initializer_list<span<char const>> args) noexcept {
|
||||
std::size_t remain = sbuf.size();
|
||||
for (auto s : args) {
|
||||
remain -= smem_cpy(sbuf.last(remain), s).size();
|
||||
}
|
||||
auto sz = sbuf.size() - remain;
|
||||
sbuf[sz] = '\0';
|
||||
return sbuf.first(sz);
|
||||
}
|
||||
|
||||
char const *as_cstr(span<char const> const &a) {
|
||||
if (a.empty()) return "";
|
||||
if (a.back() == '\0') return a.data();
|
||||
return sbuf_cpy(local_fmt_sbuf(), a).data();
|
||||
}
|
||||
|
||||
span<char> fmt_of(span<char const> const &fstr, span<char const> const &s) {
|
||||
return sbuf_cat(local_fmt_sbuf(), {"%", fstr, s});
|
||||
}
|
||||
|
||||
span<char> fmt_of_unsigned(span<char const> fstr, span<char const> const &l) {
|
||||
if (fstr.empty()) {
|
||||
return fmt_of(l, "u");
|
||||
}
|
||||
fstr = normalize(fstr);
|
||||
switch (fstr.back()) {
|
||||
case 'o':
|
||||
case 'x':
|
||||
case 'X':
|
||||
case 'u': return sbuf_cat(local_fmt_sbuf(), {"%", fstr.first(fstr.size() - 1), l, fstr.last(1)});
|
||||
default : return sbuf_cat(local_fmt_sbuf(), {"%", fstr, l, "u"});
|
||||
}
|
||||
}
|
||||
|
||||
span<char> fmt_of_signed(span<char const> fstr, span<char const> const &l) {
|
||||
if (fstr.empty()) {
|
||||
return fmt_of(l, "d");
|
||||
}
|
||||
fstr = normalize(fstr);
|
||||
switch (fstr.back()) {
|
||||
case 'o':
|
||||
case 'x':
|
||||
case 'X':
|
||||
case 'u': return fmt_of_unsigned(fstr, l);
|
||||
default : return sbuf_cat(local_fmt_sbuf(), {"%", fstr, l, "d"});
|
||||
}
|
||||
}
|
||||
|
||||
span<char> fmt_of_float(span<char const> fstr, span<char const> const &l) {
|
||||
if (fstr.empty()) {
|
||||
return fmt_of(l, "f");
|
||||
}
|
||||
fstr = normalize(fstr);
|
||||
switch (fstr.back()) {
|
||||
case 'e':
|
||||
case 'E':
|
||||
case 'g':
|
||||
case 'G': return sbuf_cat(local_fmt_sbuf(), {"%", fstr.first(fstr.size() - 1), l, fstr.last(1)});
|
||||
default : return sbuf_cat(local_fmt_sbuf(), {"%", fstr, l, "f"});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename A /*a fundamental or pointer type*/>
|
||||
int sprintf(fmt_context &ctx, span<char const> const &sfmt, A a) {
|
||||
for (std::int32_t sz = -1;;) {
|
||||
auto sbuf = ctx.buffer(sz + 1);
|
||||
if (sbuf.size() < std::size_t(sz + 1)) {
|
||||
return -1;
|
||||
}
|
||||
sz = std::snprintf(sbuf.data(), sbuf.size(), sfmt.data(), a);
|
||||
if (sz <= 0) {
|
||||
return sz;
|
||||
}
|
||||
if (std::size_t(sz) < sbuf.size()) {
|
||||
ctx.expend(sz);
|
||||
return sz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F /*a function pointer*/,
|
||||
typename A /*a fundamental or pointer type*/>
|
||||
bool sprintf(fmt_context &ctx, F fop, span<char const> const &fstr, span<char const> const &s, A a) noexcept {
|
||||
LIBIPC_TRY {
|
||||
return ipc::sprintf(ctx, fop(fstr, s), a) >= 0;
|
||||
} LIBIPC_CATCH(...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// \brief The context of fmt.
|
||||
|
||||
fmt_context::fmt_context(std::string &j) noexcept
|
||||
: joined_(j)
|
||||
, offset_(0) {}
|
||||
|
||||
std::size_t fmt_context::capacity() noexcept {
|
||||
return (offset_ < sbuf_.size()) ? sbuf_.size() : joined_.size();
|
||||
}
|
||||
|
||||
void fmt_context::reset() noexcept {
|
||||
offset_ = 0;
|
||||
}
|
||||
|
||||
bool fmt_context::finish() noexcept {
|
||||
LIBIPC_TRY {
|
||||
if (offset_ < sbuf_.size()) {
|
||||
joined_.assign(sbuf_.data(), offset_);
|
||||
} else {
|
||||
joined_.resize(offset_);
|
||||
}
|
||||
return true;
|
||||
} LIBIPC_CATCH(...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
span<char> fmt_context::buffer(std::size_t sz) noexcept {
|
||||
auto roundup = [](std::size_t sz) noexcept {
|
||||
constexpr std::size_t fmt_context_aligned_size = 512U;
|
||||
return (sz & ~(fmt_context_aligned_size - 1)) + fmt_context_aligned_size;
|
||||
};
|
||||
auto sbuf = make_span(sbuf_);
|
||||
LIBIPC_TRY {
|
||||
if (offset_ < sbuf.size()) {
|
||||
if ((offset_ + sz) < sbuf.size()) {
|
||||
return sbuf.subspan(offset_);
|
||||
} else {
|
||||
/// \remark switch the cache to std::string
|
||||
joined_.assign(sbuf.data(), offset_);
|
||||
joined_.resize(roundup(offset_ + sz));
|
||||
}
|
||||
} else if ((offset_ + sz) >= joined_.size()) {
|
||||
joined_.resize(roundup(offset_ + sz));
|
||||
}
|
||||
return {&joined_[offset_], joined_.size() - offset_};
|
||||
} LIBIPC_CATCH(...) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void fmt_context::expend(std::size_t sz) noexcept {
|
||||
offset_ += sz;
|
||||
}
|
||||
|
||||
bool fmt_context::append(span<char const> const &str) noexcept {
|
||||
auto sz = str.size();
|
||||
if (sz == 0) return true;
|
||||
if (str.back() == '\0') --sz;
|
||||
auto sbuf = buffer(sz);
|
||||
if (sbuf.size() < sz) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(sbuf.data(), str.data(), sz);
|
||||
offset_ += sz;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// \brief To string conversion.
|
||||
|
||||
bool to_string(fmt_context &ctx, char const *a) noexcept {
|
||||
return to_string(ctx, a, {});
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, std::string const &a) noexcept {
|
||||
return ctx.append(a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, char const *a, span<char const> fstr) noexcept {
|
||||
if (a == nullptr) {
|
||||
return ipc::sprintf(ctx, fmt_of, fstr, "s", "");
|
||||
} else {
|
||||
return ipc::sprintf(ctx, fmt_of, fstr, "s", a);
|
||||
}
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, char a) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of, {}, "c", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, wchar_t a) noexcept {
|
||||
LIBIPC_TRY {
|
||||
std::string des;
|
||||
cvt_sstr(std::wstring{a}, des);
|
||||
return ctx.append(des);
|
||||
} LIBIPC_CATCH(...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, char16_t a) noexcept {
|
||||
LIBIPC_TRY {
|
||||
std::string des;
|
||||
cvt_sstr(std::u16string{a}, des);
|
||||
return ctx.append(des);
|
||||
} LIBIPC_CATCH(...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, char32_t a) noexcept {
|
||||
LIBIPC_TRY {
|
||||
std::string des;
|
||||
cvt_sstr(std::u32string{a}, des);
|
||||
return ctx.append(des);
|
||||
} LIBIPC_CATCH(...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, signed short a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_signed, fstr, "h", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, unsigned short a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "h", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, signed int a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_signed, fstr, "", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, unsigned int a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, signed long a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_signed, fstr, "l", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, unsigned long a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "l", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, signed long long a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_signed, fstr, "ll", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, unsigned long long a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "ll", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, double a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_float, fstr, "", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, long double a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_float, fstr, "L", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, std::nullptr_t) noexcept {
|
||||
return ctx.append("null");
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, void const volatile *a) noexcept {
|
||||
if (a == nullptr) {
|
||||
return to_string(ctx, nullptr);
|
||||
}
|
||||
return ipc::sprintf(ctx, fmt_of, "", "p", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, std::tm const &a, span<char const> fstr) noexcept {
|
||||
if (fstr.empty()) {
|
||||
fstr = "%Y-%m-%d %H:%M:%S";
|
||||
}
|
||||
LIBIPC_TRY {
|
||||
std::ostringstream ss;
|
||||
ss << std::put_time(&a, as_cstr(fstr));
|
||||
return ctx.append(ss.str());
|
||||
} LIBIPC_CATCH(...) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
7
src/libipc/imp/nameof.cpp
Normal file
7
src/libipc/imp/nameof.cpp
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#if defined(LIBIPC_CC_GNUC)
|
||||
# include "libipc/platform/gnuc/demangle.h"
|
||||
#else
|
||||
# include "libipc/platform/win/demangle.h"
|
||||
#endif
|
||||
7
src/libipc/imp/system.cpp
Normal file
7
src/libipc/imp/system.cpp
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#if defined(LIBIPC_OS_WIN)
|
||||
# include "libipc/platform/win/system.h"
|
||||
#else
|
||||
# include "libipc/platform/posix/system.h"
|
||||
#endif
|
||||
@ -14,7 +14,6 @@
|
||||
#include "libipc/ipc.h"
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/shm.h"
|
||||
#include "libipc/pool_alloc.h"
|
||||
#include "libipc/queue.h"
|
||||
#include "libipc/policy.h"
|
||||
#include "libipc/rw_lock.h"
|
||||
@ -25,7 +24,8 @@
|
||||
#include "libipc/utility/scope_guard.h"
|
||||
#include "libipc/utility/utility.h"
|
||||
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/mem/resource.h"
|
||||
#include "libipc/mem/new.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
#include "libipc/circ/elem_array.h"
|
||||
|
||||
@ -64,25 +64,30 @@ struct msg_t : msg_t<0, AlignSize> {
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
ipc::buff_t make_cache(T& data, std::size_t size) {
|
||||
auto ptr = ipc::mem::alloc(size);
|
||||
ipc::buff_t make_cache(T &data, std::size_t size) {
|
||||
auto *ptr = ipc::mem::$new<void>(size);
|
||||
std::memcpy(ptr, &data, (ipc::detail::min)(sizeof(data), size));
|
||||
return { ptr, size, ipc::mem::free };
|
||||
return {
|
||||
ptr, size,
|
||||
[](void *p, std::size_t) noexcept {
|
||||
ipc::mem::$delete(p);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
acc_t *cc_acc(ipc::string const &pref) {
|
||||
static ipc::unordered_map<ipc::string, ipc::shm::handle> handles;
|
||||
acc_t *cc_acc(std::string const &pref) {
|
||||
static auto *phs = new ipc::unordered_map<std::string, ipc::shm::handle>; // no delete
|
||||
static std::mutex lock;
|
||||
std::lock_guard<std::mutex> guard {lock};
|
||||
auto it = handles.find(pref);
|
||||
if (it == handles.end()) {
|
||||
ipc::string shm_name {ipc::make_prefix(pref, {"CA_CONN__"})};
|
||||
auto it = phs->find(pref);
|
||||
if (it == phs->end()) {
|
||||
std::string shm_name {ipc::make_prefix(pref, "CA_CONN__")};
|
||||
ipc::shm::handle h;
|
||||
if (!h.acquire(shm_name.c_str(), sizeof(acc_t))) {
|
||||
ipc::error("[cc_acc] acquire failed: %s\n", shm_name.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
it = handles.emplace(pref, std::move(h)).first;
|
||||
it = phs->emplace(pref, std::move(h)).first;
|
||||
}
|
||||
return static_cast<acc_t *>(it->second.get());
|
||||
}
|
||||
@ -105,8 +110,8 @@ struct cache_t {
|
||||
|
||||
struct conn_info_head {
|
||||
|
||||
ipc::string prefix_;
|
||||
ipc::string name_;
|
||||
std::string prefix_;
|
||||
std::string name_;
|
||||
msg_id_t cc_id_; // connection-info id
|
||||
ipc::detail::waiter cc_waiter_, wt_waiter_, rd_waiter_;
|
||||
ipc::shm::handle acc_h_;
|
||||
@ -117,10 +122,10 @@ struct conn_info_head {
|
||||
, cc_id_ {} {}
|
||||
|
||||
void init() {
|
||||
if (!cc_waiter_.valid()) cc_waiter_.open(ipc::make_prefix(prefix_, {"CC_CONN__", name_}).c_str());
|
||||
if (!wt_waiter_.valid()) wt_waiter_.open(ipc::make_prefix(prefix_, {"WT_CONN__", name_}).c_str());
|
||||
if (!rd_waiter_.valid()) rd_waiter_.open(ipc::make_prefix(prefix_, {"RD_CONN__", name_}).c_str());
|
||||
if (!acc_h_.valid()) acc_h_.acquire(ipc::make_prefix(prefix_, {"AC_CONN__", name_}).c_str(), sizeof(acc_t));
|
||||
if (!cc_waiter_.valid()) cc_waiter_.open(ipc::make_prefix(prefix_, "CC_CONN__", name_).c_str());
|
||||
if (!wt_waiter_.valid()) wt_waiter_.open(ipc::make_prefix(prefix_, "WT_CONN__", name_).c_str());
|
||||
if (!rd_waiter_.valid()) rd_waiter_.open(ipc::make_prefix(prefix_, "RD_CONN__", name_).c_str());
|
||||
if (!acc_h_.valid()) acc_h_.acquire(ipc::make_prefix(prefix_, "AC_CONN__", name_).c_str(), sizeof(acc_t));
|
||||
if (cc_id_ != 0) {
|
||||
return;
|
||||
}
|
||||
@ -146,10 +151,10 @@ struct conn_info_head {
|
||||
static void clear_storage(char const * prefix, char const * name) noexcept {
|
||||
auto p = ipc::make_string(prefix);
|
||||
auto n = ipc::make_string(name);
|
||||
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, {"CC_CONN__", n}).c_str());
|
||||
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, {"WT_CONN__", n}).c_str());
|
||||
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, {"RD_CONN__", n}).c_str());
|
||||
ipc::shm::handle::clear_storage(ipc::make_prefix(p, {"AC_CONN__", n}).c_str());
|
||||
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, "CC_CONN__", n).c_str());
|
||||
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, "WT_CONN__", n).c_str());
|
||||
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, "RD_CONN__", n).c_str());
|
||||
ipc::shm::handle::clear_storage(ipc::make_prefix(p, "AC_CONN__", n).c_str());
|
||||
}
|
||||
|
||||
void quit_waiting() {
|
||||
@ -208,10 +213,10 @@ struct chunk_info_t {
|
||||
|
||||
auto& chunk_storages() {
|
||||
class chunk_handle_t {
|
||||
ipc::unordered_map<ipc::string, ipc::shm::handle> handles_;
|
||||
ipc::unordered_map<std::string, ipc::shm::handle> handles_;
|
||||
std::mutex lock_;
|
||||
|
||||
static bool make_handle(ipc::shm::handle &h, ipc::string const &shm_name, std::size_t chunk_size) {
|
||||
static bool make_handle(ipc::shm::handle &h, std::string const &shm_name, std::size_t chunk_size) {
|
||||
if (!h.valid() &&
|
||||
!h.acquire( shm_name.c_str(),
|
||||
sizeof(chunk_info_t) + chunk_info_t::chunks_mem_size(chunk_size) )) {
|
||||
@ -223,8 +228,8 @@ auto& chunk_storages() {
|
||||
|
||||
public:
|
||||
chunk_info_t *get_info(conn_info_head *inf, std::size_t chunk_size) {
|
||||
ipc::string pref {(inf == nullptr) ? ipc::string{} : inf->prefix_};
|
||||
ipc::string shm_name {ipc::make_prefix(pref, {"CHUNK_INFO__", ipc::to_string(chunk_size)})};
|
||||
std::string pref {(inf == nullptr) ? std::string{} : inf->prefix_};
|
||||
std::string shm_name {ipc::make_prefix(pref, "CHUNK_INFO__", chunk_size)};
|
||||
ipc::shm::handle *h;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard {lock_};
|
||||
@ -243,8 +248,8 @@ auto& chunk_storages() {
|
||||
};
|
||||
using deleter_t = void (*)(chunk_handle_t*);
|
||||
using chunk_handle_ptr_t = std::unique_ptr<chunk_handle_t, deleter_t>;
|
||||
static ipc::map<std::size_t, chunk_handle_ptr_t> chunk_hs;
|
||||
return chunk_hs;
|
||||
static auto *chunk_hs = new ipc::map<std::size_t, chunk_handle_ptr_t>; // no delete
|
||||
return *chunk_hs;
|
||||
}
|
||||
|
||||
chunk_info_t *chunk_storage_info(conn_info_head *inf, std::size_t chunk_size) {
|
||||
@ -252,15 +257,15 @@ chunk_info_t *chunk_storage_info(conn_info_head *inf, std::size_t chunk_size) {
|
||||
std::decay_t<decltype(storages)>::iterator it;
|
||||
{
|
||||
static ipc::rw_lock lock;
|
||||
IPC_UNUSED_ std::shared_lock<ipc::rw_lock> guard {lock};
|
||||
LIBIPC_UNUSED std::shared_lock<ipc::rw_lock> guard {lock};
|
||||
if ((it = storages.find(chunk_size)) == storages.end()) {
|
||||
using chunk_handle_ptr_t = std::decay_t<decltype(storages)>::value_type::second_type;
|
||||
using chunk_handle_t = chunk_handle_ptr_t::element_type;
|
||||
guard.unlock();
|
||||
IPC_UNUSED_ std::lock_guard<ipc::rw_lock> guard {lock};
|
||||
LIBIPC_UNUSED std::lock_guard<ipc::rw_lock> guard {lock};
|
||||
it = storages.emplace(chunk_size, chunk_handle_ptr_t{
|
||||
ipc::mem::alloc<chunk_handle_t>(), [](chunk_handle_t *p) {
|
||||
ipc::mem::destruct(p);
|
||||
ipc::mem::$new<chunk_handle_t>(), [](chunk_handle_t *p) {
|
||||
ipc::mem::$delete(p);
|
||||
}}).first;
|
||||
}
|
||||
}
|
||||
@ -394,11 +399,11 @@ struct queue_generator {
|
||||
void init() {
|
||||
conn_info_head::init();
|
||||
if (!que_.valid()) {
|
||||
que_.open(ipc::make_prefix(prefix_, {
|
||||
que_.open(ipc::make_prefix(prefix_,
|
||||
"QU_CONN__",
|
||||
this->name_,
|
||||
"__", ipc::to_string(DataSize),
|
||||
"__", ipc::to_string(AlignSize)}).c_str());
|
||||
"__", DataSize,
|
||||
"__", AlignSize).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@ -408,11 +413,11 @@ struct queue_generator {
|
||||
}
|
||||
|
||||
static void clear_storage(char const * prefix, char const * name) noexcept {
|
||||
queue_t::clear_storage(ipc::make_prefix(ipc::make_string(prefix), {
|
||||
queue_t::clear_storage(ipc::make_prefix(prefix,
|
||||
"QU_CONN__",
|
||||
ipc::make_string(name),
|
||||
"__", ipc::to_string(DataSize),
|
||||
"__", ipc::to_string(AlignSize)}).c_str());
|
||||
name,
|
||||
"__", DataSize,
|
||||
"__", AlignSize).c_str());
|
||||
conn_info_head::clear_storage(prefix, name);
|
||||
}
|
||||
|
||||
@ -447,7 +452,7 @@ constexpr static queue_t* queue_of(ipc::handle_t h) noexcept {
|
||||
static bool connect(ipc::handle_t * ph, ipc::prefix pref, char const * name, bool start_to_recv) {
|
||||
assert(ph != nullptr);
|
||||
if (*ph == nullptr) {
|
||||
*ph = ipc::mem::alloc<conn_info_t>(pref.str, name);
|
||||
*ph = ipc::mem::$new<conn_info_t>(pref.str, name);
|
||||
}
|
||||
return reconnect(ph, start_to_recv);
|
||||
}
|
||||
@ -490,7 +495,7 @@ static bool reconnect(ipc::handle_t * ph, bool start_to_recv) {
|
||||
}
|
||||
|
||||
static void destroy(ipc::handle_t h) noexcept {
|
||||
ipc::mem::free(info_of(h));
|
||||
ipc::mem::$delete(info_of(h));
|
||||
}
|
||||
|
||||
static std::size_t recv_count(ipc::handle_t h) noexcept {
|
||||
@ -654,20 +659,20 @@ static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
|
||||
conn_info_t * inf;
|
||||
ipc::circ::cc_t curr_conns;
|
||||
ipc::circ::cc_t conn_id;
|
||||
} *r_info = ipc::mem::alloc<recycle_t>(recycle_t{
|
||||
} *r_info = ipc::mem::$new<recycle_t>(recycle_t{
|
||||
buf_id,
|
||||
inf,
|
||||
que->elems()->connections(std::memory_order_relaxed),
|
||||
que->connected_id()
|
||||
});
|
||||
if (r_info == nullptr) {
|
||||
ipc::log("fail: ipc::mem::alloc<recycle_t>.\n");
|
||||
ipc::log("fail: ipc::mem::$new<recycle_t>.\n");
|
||||
return ipc::buff_t{buf, msg_size}; // no recycle
|
||||
} else {
|
||||
return ipc::buff_t{buf, msg_size, [](void* p_info, std::size_t size) {
|
||||
auto r_info = static_cast<recycle_t *>(p_info);
|
||||
IPC_UNUSED_ auto finally = ipc::guard([r_info] {
|
||||
ipc::mem::free(r_info);
|
||||
LIBIPC_UNUSED auto finally = ipc::guard([r_info] {
|
||||
ipc::mem::$delete(r_info);
|
||||
});
|
||||
recycle_storage<flag_t>(r_info->storage_id,
|
||||
r_info->inf,
|
||||
|
||||
53
src/libipc/mem/bytes_allocator.cpp
Normal file
53
src/libipc/mem/bytes_allocator.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
|
||||
#include <algorithm> // std::swap
|
||||
|
||||
#include "libipc/imp/log.h"
|
||||
#include "libipc/mem/bytes_allocator.h"
|
||||
#include "libipc/mem/memory_resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
bytes_allocator::holder_mr_base &bytes_allocator::get_holder() noexcept {
|
||||
return *reinterpret_cast<holder_mr_base *>(holder_.data());
|
||||
}
|
||||
|
||||
bytes_allocator::holder_mr_base const &bytes_allocator::get_holder() const noexcept {
|
||||
return *reinterpret_cast<holder_mr_base const *>(holder_.data());
|
||||
}
|
||||
|
||||
void bytes_allocator::init_default_resource() noexcept {
|
||||
std::ignore = ipc::construct<holder_mr<new_delete_resource>>(holder_.data(), new_delete_resource::get());
|
||||
}
|
||||
|
||||
bytes_allocator::bytes_allocator() noexcept
|
||||
: bytes_allocator(new_delete_resource::get()) {}
|
||||
|
||||
bytes_allocator::~bytes_allocator() noexcept {
|
||||
ipc::destroy(&get_holder());
|
||||
}
|
||||
|
||||
void bytes_allocator::swap(bytes_allocator &other) noexcept {
|
||||
std::swap(this->holder_, other.holder_);
|
||||
}
|
||||
|
||||
void *bytes_allocator::allocate(std::size_t s, std::size_t a) const {
|
||||
LIBIPC_LOG();
|
||||
if ((a & (a - 1)) != 0) {
|
||||
log.error("failed: allocate alignment is not a power of 2.");
|
||||
return nullptr;
|
||||
}
|
||||
return get_holder().alloc(s, a);
|
||||
}
|
||||
|
||||
void bytes_allocator::deallocate(void *p, std::size_t s, std::size_t a) const {
|
||||
LIBIPC_LOG();
|
||||
if ((a & (a - 1)) != 0) {
|
||||
log.error("failed: allocate alignment is not a power of 2.");
|
||||
return;
|
||||
}
|
||||
get_holder().dealloc(p, s, a);
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
47
src/libipc/mem/central_cache_allocator.cpp
Normal file
47
src/libipc/mem/central_cache_allocator.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
#include <mutex>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/imp/byte.h"
|
||||
#include "libipc/mem/bytes_allocator.h"
|
||||
#include "libipc/mem/memory_resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
class thread_safe_resource : public monotonic_buffer_resource {
|
||||
public:
|
||||
thread_safe_resource(span<byte> buffer) noexcept
|
||||
: monotonic_buffer_resource(buffer) {}
|
||||
|
||||
~thread_safe_resource() noexcept {
|
||||
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
|
||||
monotonic_buffer_resource::release();
|
||||
}
|
||||
|
||||
void *allocate(std::size_t bytes, std::size_t alignment) noexcept {
|
||||
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
|
||||
return monotonic_buffer_resource::allocate(bytes, alignment);
|
||||
}
|
||||
|
||||
void deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
|
||||
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
|
||||
monotonic_buffer_resource::deallocate(p, bytes, alignment);
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
bytes_allocator ¢ral_cache_allocator() noexcept {
|
||||
static std::array<byte, central_cache_default_size> buf;
|
||||
static thread_safe_resource res(buf);
|
||||
static bytes_allocator a(&res);
|
||||
return a;
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
140
src/libipc/mem/monotonic_buffer_resource.cpp
Normal file
140
src/libipc/mem/monotonic_buffer_resource.cpp
Normal file
@ -0,0 +1,140 @@
|
||||
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
|
||||
#include "libipc/imp/log.h"
|
||||
#include "libipc/imp/aligned.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/mem/memory_resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
namespace {
|
||||
|
||||
template <typename Node>
|
||||
Node *make_node(bytes_allocator const &upstream, std::size_t initial_size, std::size_t alignment) noexcept {
|
||||
LIBIPC_LOG();
|
||||
auto sz = ipc::round_up(sizeof(Node), alignment) + initial_size;
|
||||
LIBIPC_TRY {
|
||||
auto *node = static_cast<Node *>(upstream.allocate(sz));
|
||||
if (node == nullptr) {
|
||||
log.error("failed: allocate memory for `monotonic_buffer_resource`'s node.",
|
||||
" bytes = ", initial_size, ", alignment = ", alignment);
|
||||
return nullptr;
|
||||
}
|
||||
node->next = nullptr;
|
||||
node->size = sz;
|
||||
return node;
|
||||
} LIBIPC_CATCH(...) {
|
||||
log.error("failed: allocate memory for `monotonic_buffer_resource`'s node.",
|
||||
" bytes = ", initial_size, ", alignment = ", alignment,
|
||||
"\n\texception: ", ipc::log::exception_string(std::current_exception()));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t next_buffer_size(std::size_t size) noexcept {
|
||||
return size * 3 / 2;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
monotonic_buffer_resource::monotonic_buffer_resource() noexcept
|
||||
: monotonic_buffer_resource(bytes_allocator{}) {}
|
||||
|
||||
monotonic_buffer_resource::monotonic_buffer_resource(bytes_allocator upstream) noexcept
|
||||
: monotonic_buffer_resource(0, std::move(upstream)) {}
|
||||
|
||||
monotonic_buffer_resource::monotonic_buffer_resource(std::size_t initial_size) noexcept
|
||||
: monotonic_buffer_resource(initial_size, bytes_allocator{}) {}
|
||||
|
||||
monotonic_buffer_resource::monotonic_buffer_resource(std::size_t initial_size, bytes_allocator upstream) noexcept
|
||||
: upstream_ (std::move(upstream))
|
||||
, free_list_ (nullptr)
|
||||
, head_ (nullptr)
|
||||
, tail_ (nullptr)
|
||||
, next_size_ (initial_size)
|
||||
, initial_buffer_(nullptr)
|
||||
, initial_size_ (initial_size) {}
|
||||
|
||||
monotonic_buffer_resource::monotonic_buffer_resource(ipc::span<ipc::byte> buffer) noexcept
|
||||
: monotonic_buffer_resource(buffer, bytes_allocator{}) {}
|
||||
|
||||
monotonic_buffer_resource::monotonic_buffer_resource(ipc::span<ipc::byte> buffer, bytes_allocator upstream) noexcept
|
||||
: upstream_ (std::move(upstream))
|
||||
, free_list_ (nullptr)
|
||||
, head_ (buffer.begin())
|
||||
, tail_ (buffer.end())
|
||||
, next_size_ (next_buffer_size(buffer.size()))
|
||||
, initial_buffer_(buffer.begin())
|
||||
, initial_size_ (buffer.size()) {}
|
||||
|
||||
monotonic_buffer_resource::~monotonic_buffer_resource() noexcept {
|
||||
release();
|
||||
}
|
||||
|
||||
bytes_allocator monotonic_buffer_resource::upstream_resource() const noexcept {
|
||||
return upstream_;
|
||||
}
|
||||
|
||||
void monotonic_buffer_resource::release() noexcept {
|
||||
LIBIPC_LOG();
|
||||
LIBIPC_TRY {
|
||||
while (free_list_ != nullptr) {
|
||||
auto *next = free_list_->next;
|
||||
upstream_.deallocate(free_list_, free_list_->size);
|
||||
free_list_ = next;
|
||||
}
|
||||
} LIBIPC_CATCH(...) {
|
||||
log.error("failed: deallocate memory for `monotonic_buffer_resource`.",
|
||||
"\n\texception: ", ipc::log::exception_string(std::current_exception()));
|
||||
}
|
||||
// reset to initial state at contruction
|
||||
if ((head_ = initial_buffer_) != nullptr) {
|
||||
tail_ = head_ + initial_size_;
|
||||
next_size_ = next_buffer_size(initial_size_);
|
||||
} else {
|
||||
tail_ = nullptr;
|
||||
next_size_ = initial_size_;
|
||||
}
|
||||
}
|
||||
|
||||
void *monotonic_buffer_resource::allocate(std::size_t bytes, std::size_t alignment) noexcept {
|
||||
LIBIPC_LOG();
|
||||
if (bytes == 0) {
|
||||
log.error("failed: allocate bytes = 0.");
|
||||
return nullptr;
|
||||
}
|
||||
void *p = head_;
|
||||
auto s = static_cast<std::size_t>(tail_ - head_);
|
||||
if (std::align(alignment, bytes, p, s) == nullptr) {
|
||||
next_size_ = (std::max)(next_size_, bytes);
|
||||
auto *node = make_node<monotonic_buffer_resource::node>(upstream_, next_size_, alignment);
|
||||
if (node == nullptr) return nullptr;
|
||||
node->next = free_list_;
|
||||
free_list_ = node;
|
||||
next_size_ = next_buffer_size(next_size_);
|
||||
// try again
|
||||
s = node->size - sizeof(monotonic_buffer_resource::node);
|
||||
p = std::align(alignment, bytes, (p = node + 1), s);
|
||||
if (p == nullptr) {
|
||||
log.error("failed: allocate memory for `monotonic_buffer_resource`.",
|
||||
" bytes = ", bytes, ", alignment = ", alignment);
|
||||
return nullptr;
|
||||
}
|
||||
tail_ = static_cast<ipc::byte *>(p) + s;
|
||||
}
|
||||
head_ = static_cast<ipc::byte *>(p) + bytes;
|
||||
return p;
|
||||
}
|
||||
|
||||
void monotonic_buffer_resource::deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
|
||||
static_cast<void>(p);
|
||||
static_cast<void>(bytes);
|
||||
static_cast<void>(alignment);
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
121
src/libipc/mem/new.cpp
Normal file
121
src/libipc/mem/new.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
|
||||
#include "libipc/mem/new.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
/// \brief Select the incremental level based on the size.
|
||||
constexpr inline std::size_t regular_level(std::size_t s) noexcept {
|
||||
return (s <= 128 ) ? 0 :
|
||||
(s <= 1024 ) ? 1 :
|
||||
(s <= 8192 ) ? 2 :
|
||||
(s <= 65536) ? 3 : 4;
|
||||
}
|
||||
|
||||
/// \brief Use block pools to handle memory less than 64K.
|
||||
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
|
||||
class block_resource_base : public block_pool<BlockSize, BlockPoolExpansion>
|
||||
, public block_collector {
|
||||
public:
|
||||
void *allocate(std::size_t /*bytes*/) noexcept override {
|
||||
return block_pool<BlockSize, BlockPoolExpansion>::allocate();
|
||||
}
|
||||
|
||||
void deallocate(void *p, std::size_t /*bytes*/) noexcept override {
|
||||
block_pool<BlockSize, BlockPoolExpansion>::deallocate(p);
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Use `new`/`delete` to handle memory larger than 64K.
|
||||
template <>
|
||||
class block_resource_base<0, 0> : public new_delete_resource
|
||||
, public block_collector {
|
||||
public:
|
||||
void *allocate(std::size_t bytes) noexcept override {
|
||||
return new_delete_resource::allocate(bytes);
|
||||
}
|
||||
|
||||
void deallocate(void *p, std::size_t bytes) noexcept override {
|
||||
new_delete_resource::deallocate(p, bytes);
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Defines block pool memory resource based on block pool.
|
||||
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
|
||||
class block_pool_resource : public block_resource_base<BlockSize, BlockPoolExpansion> {
|
||||
public:
|
||||
static block_collector &get() noexcept {
|
||||
thread_local block_pool_resource instance;
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Matches the appropriate memory block resource based on a specified size.
|
||||
block_collector &get_regular_resource(std::size_t s) noexcept {
|
||||
std::size_t l = regular_level(s);
|
||||
switch (l) {
|
||||
case 0:
|
||||
switch (round_up<std::size_t>(s, 16)) {
|
||||
case 16 : return block_pool_resource<16 , 512>::get();
|
||||
case 32 : return block_pool_resource<32 , 512>::get();
|
||||
case 48 : return block_pool_resource<48 , 512>::get();
|
||||
case 64 : return block_pool_resource<64 , 512>::get();
|
||||
case 80 : return block_pool_resource<80 , 512>::get();
|
||||
case 96 : return block_pool_resource<96 , 512>::get();
|
||||
case 112: return block_pool_resource<112, 512>::get();
|
||||
case 128: return block_pool_resource<128, 512>::get();
|
||||
default : break;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
switch (round_up<std::size_t>(s, 128)) {
|
||||
case 256 : return block_pool_resource<256 , 256>::get();
|
||||
case 384 : return block_pool_resource<384 , 256>::get();
|
||||
case 512 : return block_pool_resource<512 , 256>::get();
|
||||
case 640 : return block_pool_resource<640 , 256>::get();
|
||||
case 768 : return block_pool_resource<768 , 256>::get();
|
||||
case 896 : return block_pool_resource<896 , 256>::get();
|
||||
case 1024: return block_pool_resource<1024, 256>::get();
|
||||
default : break;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
switch (round_up<std::size_t>(s, 1024)) {
|
||||
case 2048: return block_pool_resource<2048, 128>::get();
|
||||
case 3072: return block_pool_resource<3072, 128>::get();
|
||||
case 4096: return block_pool_resource<4096, 128>::get();
|
||||
case 5120: return block_pool_resource<5120, 128>::get();
|
||||
case 6144: return block_pool_resource<6144, 128>::get();
|
||||
case 7168: return block_pool_resource<7168, 128>::get();
|
||||
case 8192: return block_pool_resource<8192, 128>::get();
|
||||
default : break;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
switch (round_up<std::size_t>(s, 8192)) {
|
||||
case 16384: return block_pool_resource<16384, 64>::get();
|
||||
case 24576: return block_pool_resource<24576, 64>::get();
|
||||
case 32768: return block_pool_resource<32768, 64>::get();
|
||||
case 40960: return block_pool_resource<40960, 64>::get();
|
||||
case 49152: return block_pool_resource<49152, 64>::get();
|
||||
case 57344: return block_pool_resource<57344, 64>::get();
|
||||
case 65536: return block_pool_resource<65536, 64>::get();
|
||||
default : break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return block_pool_resource<0, 0>::get();
|
||||
}
|
||||
|
||||
void *alloc(std::size_t bytes) noexcept {
|
||||
return get_regular_resource(bytes).allocate(bytes);
|
||||
}
|
||||
|
||||
void free(void *p, std::size_t bytes) noexcept {
|
||||
return get_regular_resource(bytes).deallocate(p, bytes);
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
101
src/libipc/mem/new_delete_resource.cpp
Normal file
101
src/libipc/mem/new_delete_resource.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
|
||||
#include <cstdlib> // std::aligned_alloc
|
||||
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/imp/aligned.h"
|
||||
#include "libipc/imp/system.h"
|
||||
#include "libipc/imp/log.h"
|
||||
#include "libipc/mem/memory_resource.h"
|
||||
#include "libipc/mem/verify_args.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
/**
|
||||
* \brief Returns a pointer to a new_delete_resource.
|
||||
*
|
||||
* \return new_delete_resource*
|
||||
*/
|
||||
new_delete_resource *new_delete_resource::get() noexcept {
|
||||
static new_delete_resource mem_res;
|
||||
return &mem_res;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment.
|
||||
* Alignment shall be a power of two.
|
||||
*
|
||||
* \see https://en.cppreference.com/w/cpp/memory/memory_resource/do_allocate
|
||||
* https://www.cppstories.com/2019/08/newnew-align/
|
||||
*
|
||||
* \return void * - nullptr if storage of the requested size and alignment cannot be obtained.
|
||||
*/
|
||||
void *new_delete_resource::allocate(std::size_t bytes, std::size_t alignment) noexcept {
|
||||
LIBIPC_LOG();
|
||||
if (!verify_args(bytes, alignment)) {
|
||||
log.error("invalid bytes = ", bytes, ", alignment = ", alignment);
|
||||
return nullptr;
|
||||
}
|
||||
#if defined(LIBIPC_CPP_17)
|
||||
/// \see https://en.cppreference.com/w/cpp/memory/c/aligned_alloc
|
||||
/// \remark The size parameter must be an integral multiple of alignment.
|
||||
return std::aligned_alloc(alignment, ipc::round_up(bytes, alignment));
|
||||
#else
|
||||
if (alignment <= alignof(std::max_align_t)) {
|
||||
/// \see https://en.cppreference.com/w/cpp/memory/c/malloc
|
||||
return std::malloc(bytes);
|
||||
}
|
||||
#if defined(LIBIPC_OS_WIN)
|
||||
/// \see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc
|
||||
return ::_aligned_malloc(bytes, alignment);
|
||||
#else // try posix
|
||||
/// \see https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_memalign.html
|
||||
void *p = nullptr;
|
||||
int ret = ::posix_memalign(&p, alignment, bytes);
|
||||
if (ret != 0) {
|
||||
log.error("failed: posix_memalign(alignment = ", alignment,
|
||||
", bytes = ", bytes,
|
||||
"). error = ", sys::error(ret));
|
||||
return nullptr;
|
||||
}
|
||||
return p;
|
||||
#endif
|
||||
#endif // defined(LIBIPC_CPP_17)
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Deallocates the storage pointed to by p.
|
||||
* The storage it points to must not yet have been deallocated, otherwise the behavior is undefined.
|
||||
*
|
||||
* \see https://en.cppreference.com/w/cpp/memory/memory_resource/do_deallocate
|
||||
*
|
||||
* \param p must have been returned by a prior call to new_delete_resource::do_allocate(bytes, alignment).
|
||||
*/
|
||||
void new_delete_resource::deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
|
||||
LIBIPC_LOG();
|
||||
if (p == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (!verify_args(bytes, alignment)) {
|
||||
log.error("invalid bytes = ", bytes, ", alignment = ", alignment);
|
||||
return;
|
||||
}
|
||||
#if defined(LIBIPC_CPP_17)
|
||||
/// \see https://en.cppreference.com/w/cpp/memory/c/free
|
||||
std::free(p);
|
||||
#else
|
||||
if (alignment <= alignof(std::max_align_t)) {
|
||||
std::free(p);
|
||||
return;
|
||||
}
|
||||
#if defined(LIBIPC_OS_WIN)
|
||||
/// \see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-free
|
||||
::_aligned_free(p);
|
||||
#else // try posix
|
||||
::free(p);
|
||||
#endif
|
||||
#endif // defined(LIBIPC_CPP_17)
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
39
src/libipc/mem/resource.h
Executable file
39
src/libipc/mem/resource.h
Executable file
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/imp/fmt.h"
|
||||
#include "libipc/mem/container_allocator.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
template <typename Key, typename T>
|
||||
using unordered_map = std::unordered_map<
|
||||
Key, T, std::hash<Key>, std::equal_to<Key>, ipc::mem::container_allocator<std::pair<Key const, T>>
|
||||
>;
|
||||
|
||||
template <typename Key, typename T>
|
||||
using map = std::map<
|
||||
Key, T, std::less<Key>, ipc::mem::container_allocator<std::pair<Key const, T>>
|
||||
>;
|
||||
|
||||
/// \brief Check string validity.
|
||||
constexpr bool is_valid_string(char const *str) noexcept {
|
||||
return (str != nullptr) && (str[0] != '\0');
|
||||
}
|
||||
|
||||
/// \brief Make a valid string.
|
||||
inline std::string make_string(char const *str) {
|
||||
return is_valid_string(str) ? std::string{str} : std::string{};
|
||||
}
|
||||
|
||||
/// \brief Combine prefix from a list of strings.
|
||||
template <typename A1, typename... A>
|
||||
inline std::string make_prefix(A1 &&prefix, A &&...args) {
|
||||
return ipc::fmt(std::forward<A1>(prefix), "__IPC_SHM__", std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
16
src/libipc/mem/verify_args.h
Normal file
16
src/libipc/mem/verify_args.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
/**
|
||||
* \brief Check that bytes is not 0 and that the alignment is a power of two.
|
||||
*/
|
||||
inline constexpr bool verify_args(std::size_t bytes, std::size_t alignment) noexcept {
|
||||
return (bytes > 0) && (alignment > 0) && ((alignment & (alignment - 1)) == 0);
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
@ -1,424 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <cstdlib>
|
||||
#include <cassert> // assert
|
||||
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/rw_lock.h"
|
||||
|
||||
#include "libipc/utility/concept.h"
|
||||
#include "libipc/memory/allocator_wrapper.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
class static_alloc {
|
||||
public:
|
||||
static void swap(static_alloc&) noexcept {}
|
||||
|
||||
static void* alloc(std::size_t size) noexcept {
|
||||
return size ? std::malloc(size) : nullptr;
|
||||
}
|
||||
|
||||
static void free(void* p) noexcept {
|
||||
std::free(p);
|
||||
}
|
||||
|
||||
static void free(void* p, std::size_t /*size*/) noexcept {
|
||||
free(p);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Scope allocation -- The destructor will release all allocated blocks.
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
constexpr std::size_t aligned(std::size_t size, size_t alignment) noexcept {
|
||||
return ( (size - 1) & ~(alignment - 1) ) + alignment;
|
||||
}
|
||||
|
||||
IPC_CONCEPT_(has_take, take(std::move(std::declval<Type>())));
|
||||
|
||||
class scope_alloc_base {
|
||||
protected:
|
||||
struct block_t {
|
||||
std::size_t size_;
|
||||
block_t * next_;
|
||||
} * head_ = nullptr, * tail_ = nullptr;
|
||||
|
||||
enum : std::size_t {
|
||||
aligned_block_size = aligned(sizeof(block_t), alignof(std::max_align_t))
|
||||
};
|
||||
|
||||
public:
|
||||
void swap(scope_alloc_base & rhs) {
|
||||
std::swap(head_, rhs.head_);
|
||||
std::swap(tail_, rhs.tail_);
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return head_ == nullptr;
|
||||
}
|
||||
|
||||
void take(scope_alloc_base && rhs) {
|
||||
if (rhs.empty()) return;
|
||||
if (empty()) swap(rhs);
|
||||
else {
|
||||
std::swap(tail_->next_, rhs.head_);
|
||||
// rhs.head_ should be nullptr here
|
||||
tail_ = rhs.tail_;
|
||||
rhs.tail_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void free(void* /*p*/) {}
|
||||
void free(void* /*p*/, std::size_t) {}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename AllocP = static_alloc>
|
||||
class scope_alloc : public detail::scope_alloc_base {
|
||||
public:
|
||||
using base_t = detail::scope_alloc_base;
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
alloc_policy alloc_;
|
||||
|
||||
void free_all() {
|
||||
while (!empty()) {
|
||||
auto curr = head_;
|
||||
head_ = head_->next_;
|
||||
alloc_.free(curr, curr->size_);
|
||||
}
|
||||
// now head_ is nullptr
|
||||
}
|
||||
|
||||
public:
|
||||
scope_alloc() = default;
|
||||
|
||||
scope_alloc(scope_alloc && rhs) { swap(rhs); }
|
||||
scope_alloc& operator=(scope_alloc rhs) { swap(rhs); return (*this); }
|
||||
|
||||
~scope_alloc() { free_all(); }
|
||||
|
||||
void swap(scope_alloc& rhs) {
|
||||
alloc_.swap(rhs.alloc_);
|
||||
base_t::swap(rhs);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(scope_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
alloc_.take(std::move(rhs.alloc_));
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(scope_alloc && rhs) -> ipc::require<!detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
std::size_t real_size = aligned_block_size + size;
|
||||
auto curr = static_cast<block_t*>(alloc_.alloc(real_size));
|
||||
curr->size_ = real_size;
|
||||
curr->next_ = head_;
|
||||
head_ = curr;
|
||||
if (tail_ == nullptr) {
|
||||
tail_ = curr;
|
||||
}
|
||||
return (reinterpret_cast<byte_t*>(curr) + aligned_block_size);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Fixed-size blocks allocation
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
class fixed_alloc_base {
|
||||
protected:
|
||||
std::size_t block_size_;
|
||||
std::size_t init_expand_;
|
||||
void * cursor_;
|
||||
|
||||
void init(std::size_t block_size, std::size_t init_expand) {
|
||||
block_size_ = block_size;
|
||||
init_expand_ = init_expand;
|
||||
cursor_ = nullptr;
|
||||
}
|
||||
|
||||
static void** node_p(void* node) {
|
||||
return reinterpret_cast<void**>(node);
|
||||
}
|
||||
|
||||
static auto& next(void* node) {
|
||||
return *node_p(node);
|
||||
}
|
||||
|
||||
public:
|
||||
bool operator<(fixed_alloc_base const & right) const {
|
||||
return init_expand_ < right.init_expand_;
|
||||
}
|
||||
|
||||
void set_block_size(std::size_t block_size) {
|
||||
block_size_ = block_size;
|
||||
}
|
||||
|
||||
void swap(fixed_alloc_base& rhs) {
|
||||
std::swap(block_size_ , rhs.block_size_);
|
||||
std::swap(init_expand_, rhs.init_expand_);
|
||||
std::swap(cursor_ , rhs.cursor_);
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return cursor_ == nullptr;
|
||||
}
|
||||
|
||||
void take(fixed_alloc_base && rhs) {
|
||||
assert(block_size_ == rhs.block_size_);
|
||||
init_expand_ = (ipc::detail::max)(init_expand_, rhs.init_expand_);
|
||||
if (rhs.empty()) return;
|
||||
auto curr = cursor_;
|
||||
if (curr != nullptr) while (1) {
|
||||
auto next_cur = next(curr);
|
||||
if (next_cur == nullptr) {
|
||||
std::swap(next(curr), rhs.cursor_);
|
||||
return;
|
||||
}
|
||||
// next_cur != nullptr
|
||||
else curr = next_cur;
|
||||
}
|
||||
// curr == nullptr, means cursor_ == nullptr
|
||||
else std::swap(cursor_, rhs.cursor_);
|
||||
// rhs.cursor_ must be nullptr
|
||||
}
|
||||
|
||||
void free(void* p) {
|
||||
if (p == nullptr) return;
|
||||
next(p) = cursor_;
|
||||
cursor_ = p;
|
||||
}
|
||||
|
||||
void free(void* p, std::size_t) {
|
||||
free(p);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename AllocP, typename ExpandP>
|
||||
class fixed_alloc : public detail::fixed_alloc_base {
|
||||
public:
|
||||
using base_t = detail::fixed_alloc_base;
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
alloc_policy alloc_;
|
||||
|
||||
void* try_expand() {
|
||||
if (empty()) {
|
||||
auto size = ExpandP::next(block_size_, init_expand_);
|
||||
auto p = node_p(cursor_ = alloc_.alloc(size));
|
||||
for (std::size_t i = 0; i < (size / block_size_) - 1; ++i)
|
||||
p = node_p((*p) = reinterpret_cast<byte_t*>(p) + block_size_);
|
||||
(*p) = nullptr;
|
||||
}
|
||||
return cursor_;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit fixed_alloc(std::size_t block_size, std::size_t init_expand = 1) {
|
||||
init(block_size, init_expand);
|
||||
}
|
||||
|
||||
fixed_alloc(fixed_alloc && rhs) {
|
||||
init(0, 0);
|
||||
swap(rhs);
|
||||
}
|
||||
|
||||
fixed_alloc& operator=(fixed_alloc rhs) {
|
||||
swap(rhs);
|
||||
return (*this);
|
||||
}
|
||||
|
||||
void swap(fixed_alloc& rhs) {
|
||||
alloc_.swap(rhs.alloc_);
|
||||
base_t::swap(rhs);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(fixed_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
alloc_.take(std::move(rhs.alloc_));
|
||||
}
|
||||
|
||||
void* alloc() {
|
||||
void* p = try_expand();
|
||||
cursor_ = next(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
void* alloc(std::size_t) {
|
||||
return alloc();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <std::size_t BaseSize = sizeof(void*) * 1024,
|
||||
std::size_t LimitSize = (std::numeric_limits<std::uint32_t>::max)()>
|
||||
struct fixed_expand_policy {
|
||||
|
||||
enum : std::size_t {
|
||||
base_size = BaseSize,
|
||||
limit_size = LimitSize
|
||||
};
|
||||
|
||||
constexpr static std::size_t prev(std::size_t e) noexcept {
|
||||
return ((e / 2) == 0) ? 1 : (e / 2);
|
||||
}
|
||||
|
||||
constexpr static std::size_t next(std::size_t e) noexcept {
|
||||
return e * 2;
|
||||
}
|
||||
|
||||
static std::size_t next(std::size_t block_size, std::size_t & e) {
|
||||
auto n = ipc::detail::max<std::size_t>(block_size, base_size) * e;
|
||||
e = ipc::detail::min<std::size_t>(limit_size, next(e));
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t BlockSize,
|
||||
typename AllocP = scope_alloc<>,
|
||||
typename ExpandP = fixed_expand_policy<>>
|
||||
class fixed_alloc : public detail::fixed_alloc<AllocP, ExpandP> {
|
||||
public:
|
||||
using base_t = detail::fixed_alloc<AllocP, ExpandP>;
|
||||
|
||||
enum : std::size_t {
|
||||
block_size = ipc::detail::max<std::size_t>(BlockSize, sizeof(void*))
|
||||
};
|
||||
|
||||
public:
|
||||
explicit fixed_alloc(std::size_t init_expand)
|
||||
: base_t(block_size, init_expand) {
|
||||
}
|
||||
|
||||
fixed_alloc() : fixed_alloc(1) {}
|
||||
|
||||
fixed_alloc(fixed_alloc && rhs)
|
||||
: base_t(std::move(rhs)) {
|
||||
}
|
||||
|
||||
fixed_alloc& operator=(fixed_alloc rhs) {
|
||||
swap(rhs);
|
||||
return (*this);
|
||||
}
|
||||
|
||||
void swap(fixed_alloc& rhs) {
|
||||
base_t::swap(rhs);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Variable-size blocks allocation (without alignment)
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
class variable_alloc_base {
|
||||
protected:
|
||||
byte_t * head_ = nullptr, * tail_ = nullptr;
|
||||
|
||||
public:
|
||||
void swap(variable_alloc_base & rhs) {
|
||||
std::swap(head_, rhs.head_);
|
||||
std::swap(tail_, rhs.tail_);
|
||||
}
|
||||
|
||||
std::size_t remain() const noexcept {
|
||||
return static_cast<std::size_t>(tail_ - head_);
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return remain() == 0;
|
||||
}
|
||||
|
||||
void take(variable_alloc_base && rhs) {
|
||||
if (remain() < rhs.remain()) {
|
||||
// replace this by rhs
|
||||
head_ = rhs.head_;
|
||||
tail_ = rhs.tail_;
|
||||
}
|
||||
// discard rhs
|
||||
rhs.head_ = rhs.tail_ = nullptr;
|
||||
}
|
||||
|
||||
void free(void* /*p*/) {}
|
||||
void free(void* /*p*/, std::size_t) {}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <std::size_t ChunkSize = (sizeof(void*) * 1024), typename AllocP = scope_alloc<>>
|
||||
class variable_alloc : public detail::variable_alloc_base {
|
||||
public:
|
||||
using base_t = detail::variable_alloc_base;
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
enum : std::size_t {
|
||||
aligned_chunk_size = detail::aligned(ChunkSize, alignof(std::max_align_t))
|
||||
};
|
||||
|
||||
private:
|
||||
alloc_policy alloc_;
|
||||
|
||||
public:
|
||||
variable_alloc() = default;
|
||||
|
||||
variable_alloc(variable_alloc && rhs) { swap(rhs); }
|
||||
variable_alloc& operator=(variable_alloc rhs) { swap(rhs); return (*this); }
|
||||
|
||||
void swap(variable_alloc& rhs) {
|
||||
alloc_.swap(rhs.alloc_);
|
||||
base_t::swap(rhs);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(variable_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
alloc_.take(std::move(rhs.alloc_));
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
/*
|
||||
* byte alignment is always alignof(std::max_align_t).
|
||||
*/
|
||||
size = detail::aligned(size, alignof(std::max_align_t));
|
||||
void* ptr;
|
||||
// size would never be 0 here
|
||||
if (remain() < size) {
|
||||
std::size_t chunk_size = ipc::detail::max<std::size_t>(aligned_chunk_size, size);
|
||||
ptr = alloc_.alloc(chunk_size);
|
||||
tail_ = static_cast<byte_t*>(ptr) + chunk_size;
|
||||
head_ = tail_ - (chunk_size - size);
|
||||
}
|
||||
else {
|
||||
ptr = head_;
|
||||
head_ += size;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
@ -1,121 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <utility> // std::forward
|
||||
#include <cstddef>
|
||||
|
||||
#include "libipc/pool_alloc.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// The allocator wrapper class for STL
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T, typename AllocP>
|
||||
struct rebind {
|
||||
template <typename U>
|
||||
using alloc_t = AllocP;
|
||||
};
|
||||
|
||||
template <typename T, template <typename> class AllocT>
|
||||
struct rebind<T, AllocT<T>> {
|
||||
template <typename U>
|
||||
using alloc_t = AllocT<U>;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, typename AllocP>
|
||||
class allocator_wrapper {
|
||||
|
||||
template <typename U, typename AllocU>
|
||||
friend class allocator_wrapper;
|
||||
|
||||
public:
|
||||
// type definitions
|
||||
typedef T value_type;
|
||||
typedef value_type* pointer;
|
||||
typedef const value_type* const_pointer;
|
||||
typedef value_type& reference;
|
||||
typedef const value_type& const_reference;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef AllocP alloc_policy;
|
||||
|
||||
private:
|
||||
alloc_policy alloc_;
|
||||
|
||||
public:
|
||||
allocator_wrapper() noexcept {}
|
||||
|
||||
// construct by copying (do nothing)
|
||||
allocator_wrapper (const allocator_wrapper<T, AllocP>&) noexcept {}
|
||||
allocator_wrapper& operator=(const allocator_wrapper<T, AllocP>&) noexcept { return *this; }
|
||||
|
||||
// construct from a related allocator (do nothing)
|
||||
template <typename U, typename AllocU> allocator_wrapper (const allocator_wrapper<U, AllocU>&) noexcept {}
|
||||
template <typename U, typename AllocU> allocator_wrapper& operator=(const allocator_wrapper<U, AllocU>&) noexcept { return *this; }
|
||||
|
||||
allocator_wrapper (allocator_wrapper && rhs) noexcept : alloc_ ( std::move(rhs.alloc_) ) {}
|
||||
allocator_wrapper& operator=(allocator_wrapper && rhs) noexcept { alloc_ = std::move(rhs.alloc_); return *this; }
|
||||
|
||||
public:
|
||||
// the other type of std_allocator
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other = allocator_wrapper< U, typename detail::rebind<T, AllocP>::template alloc_t<U> >;
|
||||
};
|
||||
|
||||
constexpr size_type max_size(void) const noexcept {
|
||||
return (std::numeric_limits<size_type>::max)() / sizeof(value_type);
|
||||
}
|
||||
|
||||
public:
|
||||
pointer allocate(size_type count) noexcept {
|
||||
if (count == 0) return nullptr;
|
||||
if (count > this->max_size()) return nullptr;
|
||||
return static_cast<pointer>(alloc_.alloc(count * sizeof(value_type)));
|
||||
}
|
||||
|
||||
void deallocate(pointer p, size_type count) noexcept {
|
||||
alloc_.free(p, count * sizeof(value_type));
|
||||
}
|
||||
|
||||
template <typename... P>
|
||||
static void construct(pointer p, P && ... params) {
|
||||
ipc::mem::construct(p, std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
static void destroy(pointer p) {
|
||||
ipc::mem::destruct(p);
|
||||
}
|
||||
};
|
||||
|
||||
template <class AllocP>
|
||||
class allocator_wrapper<void, AllocP> {
|
||||
public:
|
||||
// type definitions
|
||||
typedef void value_type;
|
||||
typedef value_type* pointer;
|
||||
typedef const value_type* const_pointer;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef AllocP alloc_policy;
|
||||
};
|
||||
|
||||
template <typename T, typename U, class AllocP>
|
||||
constexpr bool operator==(const allocator_wrapper<T, AllocP>&, const allocator_wrapper<U, AllocP>&) noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, typename U, class AllocP>
|
||||
constexpr bool operator!=(const allocator_wrapper<T, AllocP>&, const allocator_wrapper<U, AllocP>&) noexcept {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
@ -1,110 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
|
||||
#include "libipc/def.h"
|
||||
|
||||
#include "libipc/memory/alloc.h"
|
||||
#include "libipc/memory/wrapper.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
//using async_pool_alloc = static_wrapper<variable_wrapper<async_wrapper<
|
||||
// detail::fixed_alloc<
|
||||
// variable_alloc <sizeof(void*) * 1024 * 256>,
|
||||
// fixed_expand_policy<sizeof(void*) * 1024, sizeof(void*) * 1024 * 256>
|
||||
// >,
|
||||
// default_recycler >>>;
|
||||
using async_pool_alloc = ipc::mem::static_alloc;
|
||||
|
||||
template <typename T>
|
||||
using allocator = allocator_wrapper<T, async_pool_alloc>;
|
||||
|
||||
} // namespace mem
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char const * pf(int) { return "%d" ; }
|
||||
constexpr char const * pf(long) { return "%ld" ; }
|
||||
constexpr char const * pf(long long) { return "%lld"; }
|
||||
constexpr char const * pf(unsigned int) { return "%u" ; }
|
||||
constexpr char const * pf(unsigned long) { return "%lu" ; }
|
||||
constexpr char const * pf(unsigned long long) { return "%llu"; }
|
||||
constexpr char const * pf(float) { return "%f" ; }
|
||||
constexpr char const * pf(double) { return "%f" ; }
|
||||
constexpr char const * pf(long double) { return "%Lf" ; }
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
template <typename T>
|
||||
struct hash : public std::hash<T> {};
|
||||
|
||||
template <typename Key, typename T>
|
||||
using unordered_map = std::unordered_map<
|
||||
Key, T, ipc::hash<Key>, std::equal_to<Key>, ipc::mem::allocator<std::pair<Key const, T>>
|
||||
>;
|
||||
|
||||
template <typename Key, typename T>
|
||||
using map = std::map<
|
||||
Key, T, std::less<Key>, ipc::mem::allocator<std::pair<Key const, T>>
|
||||
>;
|
||||
|
||||
template <typename Char>
|
||||
using basic_string = std::basic_string<
|
||||
Char, std::char_traits<Char>, ipc::mem::allocator<Char>
|
||||
>;
|
||||
|
||||
using string = basic_string<char>;
|
||||
using wstring = basic_string<wchar_t>;
|
||||
|
||||
template <> struct hash<string> {
|
||||
std::size_t operator()(string const &val) const noexcept {
|
||||
return std::hash<char const *>{}(val.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct hash<wstring> {
|
||||
std::size_t operator()(wstring const &val) const noexcept {
|
||||
return std::hash<wchar_t const *>{}(val.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
ipc::string to_string(T val) {
|
||||
char buf[std::numeric_limits<T>::digits10 + 1] {};
|
||||
if (std::snprintf(buf, sizeof(buf), pf(val), val) > 0) {
|
||||
return buf;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/// \brief Check string validity.
|
||||
constexpr bool is_valid_string(char const *str) noexcept {
|
||||
return (str != nullptr) && (str[0] != '\0');
|
||||
}
|
||||
|
||||
/// \brief Make a valid string.
|
||||
inline ipc::string make_string(char const *str) {
|
||||
return is_valid_string(str) ? ipc::string{str} : ipc::string{};
|
||||
}
|
||||
|
||||
/// \brief Combine prefix from a list of strings.
|
||||
inline ipc::string make_prefix(ipc::string prefix, std::initializer_list<ipc::string> args) {
|
||||
prefix += "__IPC_SHM__";
|
||||
for (auto const &txt: args) {
|
||||
if (txt.empty()) continue;
|
||||
prefix += txt;
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
@ -1,327 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
#include <thread>
|
||||
#include <deque> // std::deque
|
||||
#include <functional> // std::function
|
||||
#include <utility> // std::forward
|
||||
#include <cstddef>
|
||||
#include <cassert> // assert
|
||||
#include <type_traits> // std::aligned_storage_t
|
||||
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/rw_lock.h"
|
||||
#include "libipc/pool_alloc.h"
|
||||
|
||||
#include "libipc/utility/concept.h"
|
||||
#include "libipc/memory/alloc.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Thread-safe allocation wrapper
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
IPC_CONCEPT_(is_comparable, operator<(std::declval<Type>()));
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename AllocP, bool = detail::is_comparable<AllocP>::value>
|
||||
class limited_recycler;
|
||||
|
||||
template <typename AllocP>
|
||||
class limited_recycler<AllocP, true> {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
protected:
|
||||
std::deque<alloc_policy> master_allocs_;
|
||||
ipc::spin_lock master_lock_;
|
||||
|
||||
template <typename F>
|
||||
void take_first_do(F && pred) {
|
||||
auto it = master_allocs_.begin();
|
||||
pred(const_cast<alloc_policy&>(*it));
|
||||
master_allocs_.erase(it);
|
||||
}
|
||||
|
||||
public:
|
||||
void try_recover(alloc_policy & alc) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
if (master_allocs_.empty()) return;
|
||||
take_first_do([&alc](alloc_policy & first) { alc.swap(first); });
|
||||
}
|
||||
|
||||
void collect(alloc_policy && alc) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
if (master_allocs_.size() >= 32) {
|
||||
take_first_do([](alloc_policy &) {}); // erase first
|
||||
}
|
||||
master_allocs_.emplace_back(std::move(alc));
|
||||
}
|
||||
|
||||
IPC_CONSTEXPR_ auto try_replenish(alloc_policy&, std::size_t) noexcept {}
|
||||
};
|
||||
|
||||
template <typename AllocP>
|
||||
class default_recycler : public limited_recycler<AllocP> {
|
||||
|
||||
IPC_CONCEPT_(has_remain, remain());
|
||||
IPC_CONCEPT_(has_empty , empty());
|
||||
|
||||
template <typename A>
|
||||
void try_fill(A & alc) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(this->master_lock_);
|
||||
if (this->master_allocs_.empty()) return;
|
||||
this->take_first_do([&alc](alloc_policy & first) { alc.take(std::move(first)); });
|
||||
}
|
||||
|
||||
public:
|
||||
using alloc_policy = typename limited_recycler<AllocP>::alloc_policy;
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto try_replenish(alloc_policy & alc, std::size_t size)
|
||||
-> ipc::require<detail::has_take<A>::value && has_remain<A>::value> {
|
||||
if (alc.remain() >= size) return;
|
||||
this->try_fill(alc);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto try_replenish(alloc_policy & alc, std::size_t /*size*/)
|
||||
-> ipc::require<detail::has_take<A>::value && !has_remain<A>::value && has_empty<A>::value> {
|
||||
if (!alc.empty()) return;
|
||||
this->try_fill(alc);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto try_replenish(alloc_policy & alc, std::size_t /*size*/)
|
||||
-> ipc::require<!detail::has_take<A>::value && has_empty<A>::value> {
|
||||
if (!alc.empty()) return;
|
||||
this->try_recover(alc);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
IPC_CONSTEXPR_ auto try_replenish(alloc_policy & /*alc*/, std::size_t /*size*/) noexcept
|
||||
-> ipc::require<(!detail::has_take<A>::value || !has_remain<A>::value) && !has_empty<A>::value> {
|
||||
// Do Nothing.
|
||||
}
|
||||
};
|
||||
|
||||
template <typename AllocP>
|
||||
class empty_recycler {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
IPC_CONSTEXPR_ void try_recover(alloc_policy&) noexcept {}
|
||||
IPC_CONSTEXPR_ auto try_replenish(alloc_policy&, std::size_t) noexcept {}
|
||||
IPC_CONSTEXPR_ void collect(alloc_policy&&) noexcept {}
|
||||
};
|
||||
|
||||
template <typename AllocP,
|
||||
template <typename> class RecyclerP = default_recycler>
|
||||
class async_wrapper {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
RecyclerP<alloc_policy> recycler_;
|
||||
|
||||
class alloc_proxy : public AllocP {
|
||||
async_wrapper * w_ = nullptr;
|
||||
|
||||
public:
|
||||
alloc_proxy(alloc_proxy && rhs) = default;
|
||||
|
||||
template <typename ... P>
|
||||
alloc_proxy(async_wrapper* w, P && ... pars)
|
||||
: AllocP(std::forward<P>(pars) ...), w_(w) {
|
||||
assert(w_ != nullptr);
|
||||
w_->recycler_.try_recover(*this);
|
||||
}
|
||||
|
||||
~alloc_proxy() {
|
||||
w_->recycler_.collect(std::move(*this));
|
||||
}
|
||||
|
||||
auto alloc(std::size_t size) {
|
||||
w_->recycler_.try_replenish(*this, size);
|
||||
return AllocP::alloc(size);
|
||||
}
|
||||
};
|
||||
|
||||
friend class alloc_proxy;
|
||||
using ref_t = alloc_proxy&;
|
||||
|
||||
std::function<ref_t()> get_alloc_;
|
||||
|
||||
public:
|
||||
template <typename ... P>
|
||||
async_wrapper(P ... pars) {
|
||||
get_alloc_ = [this, pars ...]()->ref_t {
|
||||
thread_local alloc_proxy tls(pars ...);
|
||||
return tls;
|
||||
};
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
return get_alloc_().alloc(size);
|
||||
}
|
||||
|
||||
void free(void* p, std::size_t size) {
|
||||
get_alloc_().free(p, size);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Thread-safe allocation wrapper (with spin_lock)
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename AllocP, typename MutexT = ipc::spin_lock>
|
||||
class sync_wrapper {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
using mutex_type = MutexT;
|
||||
|
||||
private:
|
||||
mutex_type lock_;
|
||||
alloc_policy alloc_;
|
||||
|
||||
public:
|
||||
template <typename ... P>
|
||||
sync_wrapper(P && ... pars)
|
||||
: alloc_(std::forward<P>(pars) ...)
|
||||
{}
|
||||
|
||||
void swap(sync_wrapper& rhs) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
alloc_.swap(rhs.alloc_);
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
return alloc_.alloc(size);
|
||||
}
|
||||
|
||||
void free(void* p, std::size_t size) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
alloc_.free(p, size);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Variable memory allocation wrapper
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <std::size_t BaseSize = 0, std::size_t IterSize = sizeof(void*)>
|
||||
struct default_mapping_policy {
|
||||
|
||||
enum : std::size_t {
|
||||
base_size = BaseSize,
|
||||
iter_size = IterSize,
|
||||
classes_size = 64
|
||||
};
|
||||
|
||||
template <typename F, typename ... P>
|
||||
IPC_CONSTEXPR_ static void foreach(F && f, P && ... params) {
|
||||
for (std::size_t i = 0; i < classes_size; ++i) {
|
||||
f(i, std::forward<P>(params)...);
|
||||
}
|
||||
}
|
||||
|
||||
IPC_CONSTEXPR_ static std::size_t block_size(std::size_t id) noexcept {
|
||||
return (id < classes_size) ? (base_size + (id + 1) * iter_size) : 0;
|
||||
}
|
||||
|
||||
template <typename F, typename D, typename ... P>
|
||||
IPC_CONSTEXPR_ static auto classify(F && f, D && d, std::size_t size, P && ... params) {
|
||||
std::size_t id = (size - base_size - 1) / iter_size;
|
||||
return (id < classes_size) ?
|
||||
f(id, size, std::forward<P>(params)...) :
|
||||
d(size, std::forward<P>(params)...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename FixedAlloc,
|
||||
typename DefaultAlloc = mem::static_alloc,
|
||||
typename MappingP = default_mapping_policy<>>
|
||||
class variable_wrapper {
|
||||
|
||||
struct initiator {
|
||||
|
||||
using falc_t = std::aligned_storage_t<sizeof(FixedAlloc), alignof(FixedAlloc)>;
|
||||
falc_t arr_[MappingP::classes_size];
|
||||
|
||||
initiator() {
|
||||
MappingP::foreach([](std::size_t id, falc_t * a) {
|
||||
ipc::mem::construct(&initiator::at(a, id), MappingP::block_size(id));
|
||||
}, arr_);
|
||||
}
|
||||
|
||||
~initiator() {
|
||||
MappingP::foreach([](std::size_t id, falc_t * a) {
|
||||
ipc::mem::destruct(&initiator::at(a, id));
|
||||
}, arr_);
|
||||
}
|
||||
|
||||
static FixedAlloc & at(falc_t * arr, std::size_t id) noexcept {
|
||||
return reinterpret_cast<FixedAlloc&>(arr[id]);
|
||||
}
|
||||
} init_;
|
||||
|
||||
using falc_t = typename initiator::falc_t;
|
||||
|
||||
public:
|
||||
void swap(variable_wrapper & other) {
|
||||
MappingP::foreach([](std::size_t id, falc_t * in, falc_t * ot) {
|
||||
initiator::at(in, id).swap(initiator::at(ot, id));
|
||||
}, init_.arr_, other.init_.arr_);
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
return MappingP::classify([](std::size_t id, std::size_t size, falc_t * a) {
|
||||
return initiator::at(a, id).alloc(size);
|
||||
}, [](std::size_t size, falc_t *) {
|
||||
return DefaultAlloc::alloc(size);
|
||||
}, size, init_.arr_);
|
||||
}
|
||||
|
||||
void free(void* p, std::size_t size) {
|
||||
MappingP::classify([](std::size_t id, std::size_t size, void* p, falc_t * a) {
|
||||
initiator::at(a, id).free(p, size);
|
||||
}, [](std::size_t size, void* p, falc_t *) {
|
||||
DefaultAlloc::free(p, size);
|
||||
}, size, p, init_.arr_);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Static allocation wrapper
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename AllocP>
|
||||
class static_wrapper {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
static alloc_policy& instance() {
|
||||
static alloc_policy alloc;
|
||||
return alloc;
|
||||
}
|
||||
|
||||
static void swap(static_wrapper&) {}
|
||||
|
||||
static void* alloc(std::size_t size) {
|
||||
return instance().alloc(size);
|
||||
}
|
||||
|
||||
static void free(void* p, std::size_t size) {
|
||||
instance().free(p, size);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
@ -1,20 +1,7 @@
|
||||
#ifndef LIBIPC_SRC_PLATFORM_DETAIL_H_
|
||||
#define LIBIPC_SRC_PLATFORM_DETAIL_H_
|
||||
|
||||
// detect platform
|
||||
|
||||
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
|
||||
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
|
||||
defined(WINCE) || defined(_WIN32_WCE)
|
||||
# define IPC_OS_WINDOWS_
|
||||
#elif defined(__linux__) || defined(__linux)
|
||||
# define IPC_OS_LINUX_
|
||||
#elif defined(__QNX__)
|
||||
# define IPC_OS_QNX_
|
||||
#elif defined(__APPLE__)
|
||||
#elif defined(__ANDROID__)
|
||||
// TBD
|
||||
#endif
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
|
||||
@ -27,12 +14,6 @@
|
||||
|
||||
// pre-defined
|
||||
|
||||
#ifdef IPC_UNUSED_
|
||||
# error "IPC_UNUSED_ has been defined."
|
||||
#endif
|
||||
#ifdef IPC_FALLTHROUGH_
|
||||
# error "IPC_FALLTHROUGH_ has been defined."
|
||||
#endif
|
||||
#ifdef IPC_STBIND_
|
||||
# error "IPC_STBIND_ has been defined."
|
||||
#endif
|
||||
@ -42,23 +23,11 @@
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
|
||||
#define IPC_UNUSED_ [[maybe_unused]]
|
||||
#define IPC_FALLTHROUGH_ [[fallthrough]]
|
||||
#define IPC_STBIND_(A, B, ...) auto [A, B] = __VA_ARGS__
|
||||
#define IPC_CONSTEXPR_ constexpr
|
||||
|
||||
#else /*__cplusplus < 201703L*/
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define IPC_UNUSED_ __pragma(warning(suppress: 4100 4101 4189))
|
||||
#elif defined(__GNUC__)
|
||||
# define IPC_UNUSED_ __attribute__((__unused__))
|
||||
#else
|
||||
# define IPC_UNUSED_
|
||||
#endif
|
||||
|
||||
#define IPC_FALLTHROUGH_
|
||||
|
||||
#define IPC_STBIND_(A, B, ...) \
|
||||
auto tp___ = __VA_ARGS__ \
|
||||
auto A = std::get<0>(tp___); \
|
||||
|
||||
43
src/libipc/platform/gnuc/demangle.h
Normal file
43
src/libipc/platform/gnuc/demangle.h
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* \file libipc/platform/gnuc/demangle.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cxxabi.h> // abi::__cxa_demangle
|
||||
#include <cstdlib> // std::malloc
|
||||
|
||||
#include "libipc/imp/nameof.h"
|
||||
#include "libipc/imp/scope_exit.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \brief The conventional way to obtain demangled symbol name.
|
||||
* \see https://www.boost.org/doc/libs/1_80_0/libs/core/doc/html/core/demangle.html
|
||||
*
|
||||
* \param name the mangled name
|
||||
* \return std::string a human-readable demangled type name
|
||||
*/
|
||||
std::string demangle(std::string name) noexcept {
|
||||
/// \see https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html
|
||||
std::size_t sz = name.size() + 1;
|
||||
char *buffer = static_cast<char *>(std::malloc(sz));
|
||||
int status = 0;
|
||||
char *realname = abi::__cxa_demangle(name.data(), buffer, &sz, &status);
|
||||
if (realname == nullptr) {
|
||||
std::free(buffer);
|
||||
return {};
|
||||
}
|
||||
LIBIPC_SCOPE_EXIT(guard) = [realname] {
|
||||
std::free(realname);
|
||||
};
|
||||
LIBIPC_TRY {
|
||||
return std::move(name.assign(realname, sz));
|
||||
} LIBIPC_CATCH(...) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/mem/resource.h"
|
||||
#include "libipc/shm.h"
|
||||
|
||||
#include "get_wait_time.h"
|
||||
@ -108,7 +108,7 @@ class mutex {
|
||||
shm_data(init arg)
|
||||
: mtx{}, ref{0} { mtx.open(arg.name); }
|
||||
};
|
||||
ipc::map<ipc::string, shm_data> mutex_handles;
|
||||
ipc::map<std::string, shm_data> mutex_handles;
|
||||
std::mutex lock;
|
||||
|
||||
static curr_prog &get() {
|
||||
@ -122,7 +122,7 @@ class mutex {
|
||||
return;
|
||||
}
|
||||
auto &info = curr_prog::get();
|
||||
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
||||
LIBIPC_UNUSED std::lock_guard<std::mutex> guard {info.lock};
|
||||
auto it = info.mutex_handles.find(name);
|
||||
if (it == info.mutex_handles.end()) {
|
||||
it = info.mutex_handles
|
||||
@ -136,10 +136,10 @@ class mutex {
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
static void release_mutex(ipc::string const &name, F &&clear) {
|
||||
static void release_mutex(std::string const &name, F &&clear) {
|
||||
if (name.empty()) return;
|
||||
auto &info = curr_prog::get();
|
||||
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
||||
LIBIPC_UNUSED std::lock_guard<std::mutex> guard {info.lock};
|
||||
auto it = info.mutex_handles.find(name);
|
||||
if (it == info.mutex_handles.end()) {
|
||||
return;
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
#if defined(IPC_OS_WINDOWS_)
|
||||
#elif defined(IPC_OS_LINUX_)
|
||||
#if defined(LIBIPC_OS_WIN)
|
||||
#elif defined(LIBIPC_OS_LINUX)
|
||||
#include "libipc/platform/linux/a0/err.c"
|
||||
#include "libipc/platform/linux/a0/mtx.c"
|
||||
#include "libipc/platform/linux/a0/strconv.c"
|
||||
#include "libipc/platform/linux/a0/tid.c"
|
||||
#include "libipc/platform/linux/a0/time.c"
|
||||
#elif defined(IPC_OS_QNX_)
|
||||
#elif defined(LIBIPC_OS_QNX)
|
||||
#else/*IPC_OS*/
|
||||
# error "Unsupported platform."
|
||||
#endif
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
#if defined(IPC_OS_WINDOWS_)
|
||||
#if defined(LIBIPC_OS_WIN)
|
||||
#include "libipc/platform/win/shm_win.cpp"
|
||||
#elif defined(IPC_OS_LINUX_) || defined(IPC_OS_QNX_)
|
||||
#elif defined(LIBIPC_OS_LINUX) || defined(LIBIPC_OS_QNX)
|
||||
#include "libipc/platform/posix/shm_posix.cpp"
|
||||
#else/*IPC_OS*/
|
||||
# error "Unsupported platform."
|
||||
|
||||
@ -63,7 +63,7 @@ public:
|
||||
ipc::error("fail pthread_condattr_init[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
IPC_UNUSED_ auto guard_cond_attr = guard([&cond_attr] { ::pthread_condattr_destroy(&cond_attr); });
|
||||
LIBIPC_UNUSED auto guard_cond_attr = guard([&cond_attr] { ::pthread_condattr_destroy(&cond_attr); });
|
||||
if ((eno = ::pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED)) != 0) {
|
||||
ipc::error("fail pthread_condattr_setpshared[%d]\n", eno);
|
||||
return false;
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
#include "libipc/platform/detail.h"
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/utility/scope_guard.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/mem/resource.h"
|
||||
#include "libipc/shm.h"
|
||||
|
||||
#include "get_wait_time.h"
|
||||
@ -38,7 +38,7 @@ class mutex {
|
||||
shm_data(init arg)
|
||||
: shm{arg.name, arg.size}, ref{0} {}
|
||||
};
|
||||
ipc::map<ipc::string, shm_data> mutex_handles;
|
||||
ipc::map<std::string, shm_data> mutex_handles;
|
||||
std::mutex lock;
|
||||
|
||||
static curr_prog &get() {
|
||||
@ -52,7 +52,7 @@ class mutex {
|
||||
return nullptr;
|
||||
}
|
||||
auto &info = curr_prog::get();
|
||||
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
||||
LIBIPC_UNUSED std::lock_guard<std::mutex> guard {info.lock};
|
||||
auto it = info.mutex_handles.find(name);
|
||||
if (it == info.mutex_handles.end()) {
|
||||
it = info.mutex_handles
|
||||
@ -71,10 +71,10 @@ class mutex {
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
static void release_mutex(ipc::string const &name, F &&clear) {
|
||||
static void release_mutex(std::string const &name, F &&clear) {
|
||||
if (name.empty()) return;
|
||||
auto &info = curr_prog::get();
|
||||
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
||||
LIBIPC_UNUSED std::lock_guard<std::mutex> guard {info.lock};
|
||||
auto it = info.mutex_handles.find(name);
|
||||
if (it == info.mutex_handles.end()) {
|
||||
return;
|
||||
@ -130,7 +130,7 @@ public:
|
||||
ipc::error("fail pthread_mutexattr_init[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
IPC_UNUSED_ auto guard_mutex_attr = guard([&mutex_attr] { ::pthread_mutexattr_destroy(&mutex_attr); });
|
||||
LIBIPC_UNUSED auto guard_mutex_attr = guard([&mutex_attr] { ::pthread_mutexattr_destroy(&mutex_attr); });
|
||||
if ((eno = ::pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED)) != 0) {
|
||||
ipc::error("fail pthread_mutexattr_setpshared[%d]\n", eno);
|
||||
return false;
|
||||
|
||||
@ -13,10 +13,10 @@
|
||||
|
||||
#include "libipc/shm.h"
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/pool_alloc.h"
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/mem/resource.h"
|
||||
#include "libipc/mem/new.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@ -28,7 +28,7 @@ struct id_info_t {
|
||||
int fd_ = -1;
|
||||
void* mem_ = nullptr;
|
||||
std::size_t size_ = 0;
|
||||
ipc::string name_;
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
constexpr std::size_t calc_size(std::size_t size) {
|
||||
@ -51,7 +51,7 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
||||
}
|
||||
// For portable use, a shared memory object should be identified by name of the form /somename.
|
||||
// see: https://man7.org/linux/man-pages/man3/shm_open.3.html
|
||||
ipc::string op_name = ipc::string{"/"} + name;
|
||||
std::string op_name = std::string{"/"} + name;
|
||||
// Open the object for read-write access.
|
||||
int flag = O_RDWR;
|
||||
switch (mode) {
|
||||
@ -81,7 +81,7 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
||||
::fchmod(fd, S_IRUSR | S_IWUSR |
|
||||
S_IRGRP | S_IWGRP |
|
||||
S_IROTH | S_IWOTH);
|
||||
auto ii = mem::alloc<id_info_t>();
|
||||
auto ii = mem::$new<id_info_t>();
|
||||
ii->fd_ = fd;
|
||||
ii->size_ = size;
|
||||
ii->name_ = std::move(op_name);
|
||||
@ -177,7 +177,7 @@ std::int32_t release(id_t id) noexcept {
|
||||
}
|
||||
}
|
||||
else ::munmap(ii->mem_, ii->size_);
|
||||
mem::free(ii);
|
||||
mem::$delete(ii);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
49
src/libipc/platform/posix/system.h
Normal file
49
src/libipc/platform/posix/system.h
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* \file libipc/platform/posix/system.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "libipc/imp/system.h"
|
||||
#include "libipc/imp/log.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace sys {
|
||||
|
||||
/**
|
||||
* \brief Get the system error number.
|
||||
* \see https://en.cppreference.com/w/cpp/error/generic_category
|
||||
* https://man7.org/linux/man-pages/man3/errno.3.html
|
||||
*/
|
||||
std::error_code error() noexcept {
|
||||
return std::error_code(errno, std::generic_category());
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Gets configuration information at run time
|
||||
* https://man7.org/linux/man-pages/man2/getpagesize.2.html
|
||||
* https://man7.org/linux/man-pages/man3/sysconf.3.html
|
||||
*/
|
||||
result<std::int64_t> conf(info r) noexcept {
|
||||
LIBIPC_LOG();
|
||||
switch (r) {
|
||||
case info::page_size: {
|
||||
auto val = ::sysconf(_SC_PAGESIZE);
|
||||
if (val >= 0) return static_cast<std::int64_t>(val);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
log.error("invalid info = ", underlyof(r));
|
||||
return std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
auto err = sys::error();
|
||||
log.error("info = ", underlyof(r), ", error = ", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
} // namespace sys
|
||||
} // namespace ipc
|
||||
95
src/libipc/platform/win/codecvt.h
Normal file
95
src/libipc/platform/win/codecvt.h
Normal file
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* \file libipc/platform/win/codecvt.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include "libipc/imp/codecvt.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \see https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
|
||||
* https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte
|
||||
*
|
||||
* CP_ACP : The system default Windows ANSI code page.
|
||||
* CP_MACCP : The current system Macintosh code page.
|
||||
* CP_OEMCP : The current system OEM code page.
|
||||
* CP_SYMBOL : Symbol code page (42).
|
||||
* CP_THREAD_ACP: The Windows ANSI code page for the current thread.
|
||||
* CP_UTF7 : UTF-7. Use this value only when forced by a 7-bit transport mechanism. Use of UTF-8 is preferred.
|
||||
* CP_UTF8 : UTF-8.
|
||||
*/
|
||||
|
||||
template <>
|
||||
std::size_t cvt_cstr(char const *src, std::size_t slen, wchar_t *des, std::size_t dlen) noexcept {
|
||||
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
|
||||
// source string is empty
|
||||
return 0;
|
||||
}
|
||||
int cch_wc = (des == nullptr) ? 0 : (int)dlen;
|
||||
int size_needed = ::MultiByteToWideChar(CP_ACP, 0, src, (int)slen, des, cch_wc);
|
||||
if (size_needed <= 0) {
|
||||
// failed: MultiByteToWideChar(CP_ACP).
|
||||
return 0;
|
||||
}
|
||||
return size_needed;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::size_t cvt_cstr(wchar_t const *src, std::size_t slen, char *des, std::size_t dlen) noexcept {
|
||||
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
|
||||
// source string is empty
|
||||
return 0;
|
||||
}
|
||||
int cb_mb = (des == nullptr) ? 0 : (int)dlen;
|
||||
int size_needed = ::WideCharToMultiByte(CP_ACP, 0, src, (int)slen, des, cb_mb, NULL, NULL);
|
||||
if (size_needed <= 0) {
|
||||
// failed: WideCharToMultiByte(CP_ACP).
|
||||
return 0;
|
||||
}
|
||||
return size_needed;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Used for char8_t (since C++20) to wchar_t conversion.
|
||||
*
|
||||
* There is no ut to guarantee correctness (I'm a little lazy here),
|
||||
* so if there are any bugs, please contact me in time.
|
||||
*/
|
||||
#if defined(LIBIMP_CPP_20)
|
||||
template <>
|
||||
std::size_t cvt_cstr(char8_t const *src, std::size_t slen, wchar_t *des, std::size_t dlen) noexcept {
|
||||
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
|
||||
// source string is empty
|
||||
return 0;
|
||||
}
|
||||
int cch_wc = (des == nullptr) ? 0 : (int)dlen;
|
||||
int size_needed = ::MultiByteToWideChar(CP_UTF8, 0, (char *)src, (int)slen, des, cch_wc);
|
||||
if (size_needed <= 0) {
|
||||
// failed: MultiByteToWideChar(CP_UTF8).
|
||||
return 0;
|
||||
}
|
||||
return size_needed;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::size_t cvt_cstr(wchar_t const *src, std::size_t slen, char8_t *des, std::size_t dlen) noexcept {
|
||||
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
|
||||
// source string is empty
|
||||
return 0;
|
||||
}
|
||||
int cb_mb = (des == nullptr) ? 0 : (int)dlen;
|
||||
int size_needed = ::WideCharToMultiByte(CP_UTF8, 0, src, (int)slen, (char *)des, cb_mb, NULL, NULL);
|
||||
if (size_needed <= 0) {
|
||||
// failed: WideCharToMultiByte(CP_UTF8).
|
||||
return 0;
|
||||
}
|
||||
return size_needed;
|
||||
}
|
||||
#endif // defined(LIBIMP_CPP_20)
|
||||
|
||||
} // namespace ipc
|
||||
@ -81,7 +81,7 @@ public:
|
||||
if (!valid()) return false;
|
||||
auto &cnt = counter();
|
||||
{
|
||||
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
|
||||
LIBIPC_UNUSED std::lock_guard<ipc::sync::mutex> guard {lock_};
|
||||
cnt = (cnt < 0) ? 1 : cnt + 1;
|
||||
}
|
||||
DWORD ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
|
||||
@ -93,7 +93,7 @@ public:
|
||||
bool rs = ::SignalObjectAndWait(mtx.native(), sem_.native(), ms, FALSE) == WAIT_OBJECT_0;
|
||||
bool rl = mtx.lock(); // INFINITE
|
||||
if (!rs) {
|
||||
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
|
||||
LIBIPC_UNUSED std::lock_guard<ipc::sync::mutex> guard {lock_};
|
||||
cnt -= 1;
|
||||
}
|
||||
return rs && rl;
|
||||
|
||||
15
src/libipc/platform/win/demangle.h
Normal file
15
src/libipc/platform/win/demangle.h
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* \file libipc/platform/win/demangle.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "libipc/imp/nameof.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
std::string demangle(std::string name) noexcept {
|
||||
return std::move(name);
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
@ -84,7 +84,7 @@ public:
|
||||
return false;
|
||||
case WAIT_ABANDONED:
|
||||
unlock();
|
||||
IPC_FALLTHROUGH_;
|
||||
LIBIPC_FALLTHROUGH;
|
||||
default:
|
||||
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
|
||||
throw std::system_error{static_cast<int>(ret), std::system_category()};
|
||||
|
||||
@ -6,10 +6,10 @@
|
||||
|
||||
#include "libipc/shm.h"
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/pool_alloc.h"
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/mem/resource.h"
|
||||
#include "libipc/mem/new.h"
|
||||
|
||||
#include "to_tchar.h"
|
||||
#include "get_sa.h"
|
||||
@ -58,7 +58,7 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
auto ii = mem::alloc<id_info_t>();
|
||||
auto ii = mem::$new<id_info_t>();
|
||||
ii->h_ = h;
|
||||
ii->size_ = size;
|
||||
return ii;
|
||||
@ -116,7 +116,7 @@ std::int32_t release(id_t id) noexcept {
|
||||
ipc::error("fail release: invalid id (h = null)\n");
|
||||
}
|
||||
else ::CloseHandle(ii->h_);
|
||||
mem::free(ii);
|
||||
mem::$delete(ii);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
88
src/libipc/platform/win/system.h
Normal file
88
src/libipc/platform/win/system.h
Normal file
@ -0,0 +1,88 @@
|
||||
/**
|
||||
* \file libipc/platform/win/system.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
#include <type_traits>
|
||||
|
||||
#include <Windows.h>
|
||||
#include <tchar.h>
|
||||
|
||||
#include "libipc/imp/system.h"
|
||||
#include "libipc/imp/log.h"
|
||||
#include "libipc/imp/codecvt.h"
|
||||
#include "libipc/imp/generic.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/imp/scope_exit.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace sys {
|
||||
|
||||
/**
|
||||
* \brief Gets a text description of the system error
|
||||
* \see https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessage
|
||||
*/
|
||||
std::string error_string(DWORD code) noexcept {
|
||||
LIBIPC_LOG();
|
||||
LIBIPC_TRY {
|
||||
LPTSTR lpErrText = NULL;
|
||||
if (::FormatMessage(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL,
|
||||
code,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPTSTR)&lpErrText,
|
||||
0, NULL) == 0) {
|
||||
log.error("failed: FormatMessage(dwMessageId = ", code, "). error = ", ::GetLastError());
|
||||
return {};
|
||||
}
|
||||
LIBIPC_SCOPE_EXIT(finally) = [lpErrText] { ::LocalFree(lpErrText); };
|
||||
std::size_t msg_len = ::_tcslen(lpErrText);
|
||||
std::size_t len = cvt_cstr(lpErrText, msg_len, (char *)nullptr, 0);
|
||||
if (len == 0) {
|
||||
return {};
|
||||
}
|
||||
std::string ret(len, '\0');
|
||||
cvt_cstr(lpErrText, msg_len, &ret[0], ret.size());
|
||||
return ret;
|
||||
} LIBIPC_CATCH(...) {
|
||||
log.error("failed: FormatMessage(dwMessageId = ", code, ").",
|
||||
"\n\texception: ", log::exception_string(std::current_exception()));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the system error number.
|
||||
* \see https://en.cppreference.com/w/cpp/error/system_category
|
||||
* https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
|
||||
*/
|
||||
std::error_code error() noexcept {
|
||||
return std::error_code(::GetLastError(), std::system_category());
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Retrieves information about the current system.
|
||||
* \see https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo
|
||||
* https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getnativesysteminfo
|
||||
*/
|
||||
result<std::int64_t> conf(info r) noexcept {
|
||||
LIBIPC_LOG();
|
||||
switch (r) {
|
||||
case info::page_size: {
|
||||
::SYSTEM_INFO info{};
|
||||
::GetNativeSystemInfo(&info);
|
||||
return (std::int64_t)info.dwPageSize;
|
||||
}
|
||||
default:
|
||||
log.error("invalid info = ", underlyof(r));
|
||||
return std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sys
|
||||
} // namespace ipc
|
||||
@ -11,7 +11,7 @@
|
||||
#include <cstddef>
|
||||
|
||||
#include "libipc/utility/concept.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/mem/resource.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
@ -36,7 +36,7 @@ using IsSameChar = ipc::require<is_same_char<T, S>::value, R>;
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T = TCHAR>
|
||||
constexpr auto to_tchar(ipc::string &&str) -> IsSameChar<T, ipc::string, ipc::string &&> {
|
||||
constexpr auto to_tchar(std::string &&str) -> IsSameChar<T, std::string, std::string &&> {
|
||||
return std::move(str); // noconv
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ constexpr auto to_tchar(ipc::string &&str) -> IsSameChar<T, ipc::string, ipc::st
|
||||
* https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
|
||||
*/
|
||||
template <typename T = TCHAR>
|
||||
auto to_tchar(ipc::string &&external) -> IsSameChar<T, ipc::wstring> {
|
||||
auto to_tchar(std::string &&external) -> IsSameChar<T, std::wstring> {
|
||||
if (external.empty()) {
|
||||
return {}; // noconv
|
||||
}
|
||||
@ -65,7 +65,7 @@ auto to_tchar(ipc::string &&external) -> IsSameChar<T, ipc::wstring> {
|
||||
if (size_needed <= 0) {
|
||||
return {};
|
||||
}
|
||||
ipc::wstring internal(size_needed, L'\0');
|
||||
std::wstring internal(size_needed, L'\0');
|
||||
::MultiByteToWideChar(CP_UTF8, 0, &external[0], (int)external.size(), &internal[0], size_needed);
|
||||
return internal;
|
||||
}
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
#include "libipc/pool_alloc.h"
|
||||
|
||||
#include "libipc/memory/resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
void* pool_alloc::alloc(std::size_t size) noexcept {
|
||||
return async_pool_alloc::alloc(size);
|
||||
}
|
||||
|
||||
void pool_alloc::free(void* p, std::size_t size) noexcept {
|
||||
async_pool_alloc::free(p, size);
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
@ -18,7 +18,7 @@
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
#include "libipc/circ/elem_def.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/mem/resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
#include "libipc/utility/pimpl.h"
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/mem/resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace shm {
|
||||
@ -16,7 +16,7 @@ public:
|
||||
shm::id_t id_ = nullptr;
|
||||
void* m_ = nullptr;
|
||||
|
||||
ipc::string n_;
|
||||
std::string n_;
|
||||
std::size_t s_ = 0;
|
||||
};
|
||||
|
||||
|
||||
@ -3,13 +3,13 @@
|
||||
|
||||
#include "libipc/utility/pimpl.h"
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/mem/resource.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
#if defined(IPC_OS_WINDOWS_)
|
||||
#if defined(LIBIPC_OS_WIN)
|
||||
#include "libipc/platform/win/condition.h"
|
||||
#elif defined(IPC_OS_LINUX_)
|
||||
#elif defined(LIBIPC_OS_LINUX)
|
||||
#include "libipc/platform/linux/condition.h"
|
||||
#elif defined(IPC_OS_QNX_)
|
||||
#elif defined(LIBIPC_OS_QNX)
|
||||
#include "libipc/platform/posix/condition.h"
|
||||
#else/*IPC_OS*/
|
||||
# error "Unsupported platform."
|
||||
|
||||
@ -3,13 +3,13 @@
|
||||
|
||||
#include "libipc/utility/pimpl.h"
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/mem/resource.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
#if defined(IPC_OS_WINDOWS_)
|
||||
#if defined(LIBIPC_OS_WIN)
|
||||
#include "libipc/platform/win/mutex.h"
|
||||
#elif defined(IPC_OS_LINUX_)
|
||||
#elif defined(LIBIPC_OS_LINUX)
|
||||
#include "libipc/platform/linux/mutex.h"
|
||||
#elif defined(IPC_OS_QNX_)
|
||||
#elif defined(LIBIPC_OS_QNX)
|
||||
#include "libipc/platform/posix/mutex.h"
|
||||
#else/*IPC_OS*/
|
||||
# error "Unsupported platform."
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
|
||||
#include "libipc/utility/pimpl.h"
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/mem/resource.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
#if defined(IPC_OS_WINDOWS_)
|
||||
#if defined(LIBIPC_OS_WIN)
|
||||
#include "libipc/platform/win/semaphore.h"
|
||||
#elif defined(IPC_OS_LINUX_) || defined(IPC_OS_QNX_)
|
||||
#elif defined(LIBIPC_OS_LINUX) || defined(LIBIPC_OS_QNX)
|
||||
#include "libipc/platform/posix/semaphore_impl.h"
|
||||
#else/*IPC_OS*/
|
||||
# error "Unsupported platform."
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
#include "libipc/waiter.h"
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
#if defined(IPC_OS_WINDOWS_)
|
||||
#if defined(LIBIPC_OS_WIN)
|
||||
#include "libipc/platform/win/mutex.h"
|
||||
#elif defined(IPC_OS_LINUX_)
|
||||
#elif defined(LIBIPC_OS_LINUX)
|
||||
#include "libipc/platform/linux/mutex.h"
|
||||
#elif defined(IPC_OS_QNX_)
|
||||
#elif defined(LIBIPC_OS_QNX)
|
||||
#include "libipc/platform/posix/mutex.h"
|
||||
#else/*IPC_OS*/
|
||||
# error "Unsupported platform."
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
#include "libipc/utility/concept.h"
|
||||
#include "libipc/pool_alloc.h"
|
||||
#include "libipc/mem/new.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
@ -36,12 +36,12 @@ IPC_CONSTEXPR_ auto clear_impl(T* p) -> IsImplComfortable<T, void> {
|
||||
|
||||
template <typename T, typename... P>
|
||||
IPC_CONSTEXPR_ auto make_impl(P&&... params) -> IsImplUncomfortable<T> {
|
||||
return mem::alloc<T>(std::forward<P>(params)...);
|
||||
return mem::$new<T>(std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
IPC_CONSTEXPR_ auto clear_impl(T* p) -> IsImplUncomfortable<T, void> {
|
||||
mem::free(p);
|
||||
mem::$delete(p);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
||||
@ -27,7 +27,7 @@ constexpr decltype(auto) static_switch(std::size_t i, F&& f, D&& def) {
|
||||
|
||||
template <typename F, std::size_t...I>
|
||||
IPC_CONSTEXPR_ void static_for(std::index_sequence<I...>, F&& f) {
|
||||
IPC_UNUSED_ auto expand = { (std::forward<F>(f)(std::integral_constant<std::size_t, I>{}), 0)... };
|
||||
LIBIPC_UNUSED auto expand = { (std::forward<F>(f)(std::integral_constant<std::size_t, I>{}), 0)... };
|
||||
}
|
||||
|
||||
template <std::size_t N, typename F>
|
||||
@ -44,18 +44,6 @@ enum {
|
||||
// #endif/*__cplusplus < 201703L*/
|
||||
};
|
||||
|
||||
template <typename T, typename U>
|
||||
auto horrible_cast(U rhs) noexcept
|
||||
-> typename std::enable_if<std::is_trivially_copyable<T>::value
|
||||
&& std::is_trivially_copyable<U>::value, T>::type {
|
||||
union {
|
||||
T t;
|
||||
U u;
|
||||
} r = {};
|
||||
r.u = rhs;
|
||||
return r.t;
|
||||
}
|
||||
|
||||
IPC_CONSTEXPR_ std::size_t make_align(std::size_t align, std::size_t size) {
|
||||
// align must be 2^n
|
||||
return (size + align - 1) & ~(align - 1);
|
||||
|
||||
@ -63,7 +63,7 @@ public:
|
||||
|
||||
template <typename F>
|
||||
bool wait_if(F &&pred, std::uint64_t tm = ipc::invalid_value) noexcept {
|
||||
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
|
||||
LIBIPC_UNUSED std::lock_guard<ipc::sync::mutex> guard {lock_};
|
||||
while ([this, &pred] {
|
||||
return !quit_.load(std::memory_order_relaxed)
|
||||
&& std::forward<F>(pred)();
|
||||
@ -75,14 +75,14 @@ public:
|
||||
|
||||
bool notify() noexcept {
|
||||
{
|
||||
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> barrier{lock_}; // barrier
|
||||
LIBIPC_UNUSED std::lock_guard<ipc::sync::mutex> barrier{lock_}; // barrier
|
||||
}
|
||||
return cond_.notify(lock_);
|
||||
}
|
||||
|
||||
bool broadcast() noexcept {
|
||||
{
|
||||
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> barrier{lock_}; // barrier
|
||||
LIBIPC_UNUSED std::lock_guard<ipc::sync::mutex> barrier{lock_}; // barrier
|
||||
}
|
||||
return cond_.broadcast(lock_);
|
||||
}
|
||||
|
||||
@ -17,6 +17,9 @@ include_directories(
|
||||
|
||||
file(GLOB SRC_FILES
|
||||
${LIBIPC_PROJECT_DIR}/test/*.cpp
|
||||
${LIBIPC_PROJECT_DIR}/test/imp/*.cpp
|
||||
${LIBIPC_PROJECT_DIR}/test/mem/*.cpp
|
||||
${LIBIPC_PROJECT_DIR}/test/concur/*.cpp
|
||||
# ${LIBIPC_PROJECT_DIR}/test/profiler/*.cpp
|
||||
)
|
||||
file(GLOB HEAD_FILES ${LIBIPC_PROJECT_DIR}/test/*.h)
|
||||
|
||||
103
test/concur/test_concur_intrusive_stack.cpp
Normal file
103
test/concur/test_concur_intrusive_stack.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#define private public
|
||||
#include "libipc/concur/intrusive_stack.h"
|
||||
|
||||
using namespace ipc;
|
||||
|
||||
TEST(intrusive_stack, construct) {
|
||||
concur::intrusive_stack<int> s;
|
||||
EXPECT_TRUE(s.empty());
|
||||
}
|
||||
|
||||
TEST(intrusive_stack, construct_node) {
|
||||
concur::intrusive_stack<int>::node n{};
|
||||
EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == nullptr);
|
||||
}
|
||||
|
||||
TEST(intrusive_stack, copyable) {
|
||||
EXPECT_FALSE(std::is_copy_constructible<concur::intrusive_stack<int>>::value);
|
||||
EXPECT_FALSE(std::is_copy_assignable<concur::intrusive_stack<int>>::value);
|
||||
}
|
||||
|
||||
TEST(intrusive_stack, moveable) {
|
||||
EXPECT_FALSE(std::is_move_constructible<concur::intrusive_stack<int>>::value);
|
||||
EXPECT_FALSE(std::is_move_assignable<concur::intrusive_stack<int>>::value);
|
||||
}
|
||||
|
||||
TEST(intrusive_stack, push_one) {
|
||||
concur::intrusive_stack<int> s;
|
||||
concur::intrusive_stack<int>::node n{123};
|
||||
s.push(&n);
|
||||
EXPECT_FALSE(s.empty());
|
||||
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == &n);
|
||||
EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == nullptr);
|
||||
EXPECT_EQ(n.value, 123);
|
||||
}
|
||||
|
||||
TEST(intrusive_stack, push_many) {
|
||||
concur::intrusive_stack<int> s;
|
||||
concur::intrusive_stack<int>::node n1{111111};
|
||||
concur::intrusive_stack<int>::node n2{222222};
|
||||
concur::intrusive_stack<int>::node n3{333333};
|
||||
s.push(&n1);
|
||||
s.push(&n2);
|
||||
s.push(&n3);
|
||||
EXPECT_FALSE(s.empty());
|
||||
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == &n3);
|
||||
EXPECT_TRUE(n3.next.load(std::memory_order_relaxed) == &n2);
|
||||
EXPECT_TRUE(n2.next.load(std::memory_order_relaxed) == &n1);
|
||||
EXPECT_TRUE(n1.next.load(std::memory_order_relaxed) == nullptr);
|
||||
EXPECT_EQ(n1.value, 111111);
|
||||
EXPECT_EQ(n2.value, 222222);
|
||||
EXPECT_EQ(n3.value, 333333);
|
||||
}
|
||||
|
||||
TEST(intrusive_stack, push_same) {
|
||||
concur::intrusive_stack<int> s;
|
||||
concur::intrusive_stack<int>::node n{321};
|
||||
s.push(&n);
|
||||
s.push(&n);
|
||||
EXPECT_FALSE(s.empty());
|
||||
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == &n);
|
||||
EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == &n);
|
||||
EXPECT_EQ(n.value, 321);
|
||||
}
|
||||
|
||||
TEST(intrusive_stack, pop_empty) {
|
||||
concur::intrusive_stack<int> s;
|
||||
EXPECT_TRUE(s.pop() == nullptr);
|
||||
}
|
||||
|
||||
TEST(intrusive_stack, pop_one) {
|
||||
concur::intrusive_stack<int> s;
|
||||
concur::intrusive_stack<int>::node n{112233};
|
||||
s.push(&n);
|
||||
EXPECT_TRUE(s.pop() == &n);
|
||||
EXPECT_TRUE(s.empty());
|
||||
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == nullptr);
|
||||
EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == nullptr);
|
||||
EXPECT_EQ(n.value, 112233);
|
||||
}
|
||||
|
||||
TEST(intrusive_stack, pop_many) {
|
||||
concur::intrusive_stack<int> s;
|
||||
concur::intrusive_stack<int>::node n1{111111};
|
||||
concur::intrusive_stack<int>::node n2{222222};
|
||||
concur::intrusive_stack<int>::node n3{333333};
|
||||
s.push(&n1);
|
||||
s.push(&n2);
|
||||
s.push(&n3);
|
||||
EXPECT_TRUE(s.pop() == &n3);
|
||||
EXPECT_TRUE(s.pop() == &n2);
|
||||
EXPECT_TRUE(s.pop() == &n1);
|
||||
EXPECT_TRUE(s.empty());
|
||||
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == nullptr);
|
||||
EXPECT_TRUE(n3.next.load(std::memory_order_relaxed) == &n2);
|
||||
EXPECT_TRUE(n2.next.load(std::memory_order_relaxed) == &n1);
|
||||
EXPECT_TRUE(n1.next.load(std::memory_order_relaxed) == nullptr);
|
||||
EXPECT_EQ(n1.value, 111111);
|
||||
EXPECT_EQ(n2.value, 222222);
|
||||
EXPECT_EQ(n3.value, 333333);
|
||||
}
|
||||
57
test/imp/test_imp_byte.cpp
Normal file
57
test/imp/test_imp_byte.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/imp/byte.h"
|
||||
#include "libipc/imp/span.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
|
||||
TEST(byte, construct) {
|
||||
{
|
||||
LIBIPC_UNUSED ipc::byte b;
|
||||
SUCCEED();
|
||||
}
|
||||
{
|
||||
ipc::byte b{};
|
||||
EXPECT_EQ(int(b), 0);
|
||||
}
|
||||
{
|
||||
ipc::byte b{123};
|
||||
EXPECT_EQ(int(b), 123);
|
||||
}
|
||||
{
|
||||
ipc::byte b{65535};
|
||||
EXPECT_EQ(int(b), 255);
|
||||
EXPECT_EQ(std::int8_t(b), -1);
|
||||
}
|
||||
{
|
||||
ipc::byte b{65536};
|
||||
EXPECT_EQ(int(b), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(byte, compare) {
|
||||
{
|
||||
ipc::byte b1{}, b2{};
|
||||
EXPECT_EQ(b1, b2);
|
||||
}
|
||||
{
|
||||
ipc::byte b1{}, b2(321);
|
||||
EXPECT_NE(b1, b2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(byte, byte_cast) {
|
||||
int a = 654321;
|
||||
int *pa = &a;
|
||||
|
||||
// int * => byte *
|
||||
ipc::byte *pb = ipc::byte_cast(pa);
|
||||
EXPECT_EQ((std::size_t)pb, (std::size_t)pa);
|
||||
|
||||
// byte * => int32_t *
|
||||
std::int32_t *pc = ipc::byte_cast<std::int32_t>(pb);
|
||||
EXPECT_EQ(*pc, a);
|
||||
|
||||
// byte alignment check
|
||||
EXPECT_EQ(ipc::byte_cast<int>(pb + 1), nullptr);
|
||||
}
|
||||
40
test/imp/test_imp_codecvt.cpp
Normal file
40
test/imp/test_imp_codecvt.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/imp/codecvt.h"
|
||||
|
||||
TEST(codecvt, cvt_cstr) {
|
||||
char const utf8[] = "hello world, 你好,こんにちは";
|
||||
wchar_t const utf16[] = L"hello world, 你好,こんにちは";
|
||||
{
|
||||
auto cvt_len = ipc::cvt_cstr(utf8, std::strlen(utf8), (wchar_t *)nullptr, 0);
|
||||
EXPECT_NE(cvt_len, 0);
|
||||
std::wstring wstr(cvt_len, L'\0');
|
||||
EXPECT_EQ(ipc::cvt_cstr(utf8, std::strlen(utf8), &wstr[0], wstr.size()), cvt_len);
|
||||
EXPECT_EQ(wstr, utf16);
|
||||
}
|
||||
{
|
||||
auto cvt_len = ipc::cvt_cstr(utf16, std::wcslen(utf16), (char *)nullptr, 0);
|
||||
EXPECT_NE(cvt_len, 0);
|
||||
std::string str(cvt_len, '\0');
|
||||
EXPECT_EQ(ipc::cvt_cstr(utf16, std::wcslen(utf16), &str[0], str.size()), cvt_len);
|
||||
EXPECT_EQ(str, utf8);
|
||||
}
|
||||
{
|
||||
auto cvt_len = ipc::cvt_cstr(utf8, std::strlen(utf8), (char *)nullptr, 0);
|
||||
EXPECT_EQ(cvt_len, std::strlen(utf8));
|
||||
std::string str(cvt_len, '\0');
|
||||
EXPECT_EQ(ipc::cvt_cstr(utf8, cvt_len, &str[0], str.size()), cvt_len);
|
||||
EXPECT_EQ(str, utf8);
|
||||
}
|
||||
{
|
||||
auto cvt_len = ipc::cvt_cstr(utf16, std::wcslen(utf16), (wchar_t *)nullptr, 0);
|
||||
EXPECT_EQ(cvt_len, std::wcslen(utf16));
|
||||
std::wstring wstr(cvt_len, u'\0');
|
||||
EXPECT_EQ(ipc::cvt_cstr(utf16, cvt_len, &wstr[0], wstr.size()), cvt_len);
|
||||
EXPECT_EQ(wstr, utf16);
|
||||
}
|
||||
}
|
||||
90
test/imp/test_imp_detect_plat.cpp
Normal file
90
test/imp/test_imp_detect_plat.cpp
Normal file
@ -0,0 +1,90 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
|
||||
TEST(detect_plat, os) {
|
||||
#if defined(LIBIPC_OS_WINCE)
|
||||
std::cout << "LIBIPC_OS_WINCE\n";
|
||||
#elif defined(LIBIPC_OS_WIN)
|
||||
std::cout << "LIBIPC_OS_WIN\n";
|
||||
#elif defined(LIBIPC_OS_LINUX)
|
||||
std::cout << "LIBIPC_OS_LINUX\n";
|
||||
#elif defined(LIBIPC_OS_QNX)
|
||||
std::cout << "LIBIPC_OS_QNX\n";
|
||||
#elif defined(LIBIPC_OS_ANDROID)
|
||||
std::cout << "LIBIPC_OS_ANDROID\n";
|
||||
#else
|
||||
ASSERT_TRUE(false);
|
||||
#endif
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(detect_plat, cc) {
|
||||
#if defined(LIBIPC_CC_MSVC)
|
||||
std::cout << "LIBIPC_CC_MSVC\n";
|
||||
#elif defined(LIBIPC_CC_GNUC)
|
||||
std::cout << "LIBIPC_CC_GNUC\n";
|
||||
#else
|
||||
ASSERT_TRUE(false);
|
||||
#endif
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(detect_plat, cpp) {
|
||||
#if defined(LIBIPC_CPP_20)
|
||||
std::cout << "LIBIPC_CPP_20\n";
|
||||
#elif defined(LIBIPC_CPP_17)
|
||||
std::cout << "LIBIPC_CPP_17\n";
|
||||
#elif defined(LIBIPC_CPP_14)
|
||||
std::cout << "LIBIPC_CPP_14\n";
|
||||
#else
|
||||
ASSERT_TRUE(false);
|
||||
#endif
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(detect_plat, byte_order) {
|
||||
auto is_endian_little = [] {
|
||||
union {
|
||||
std::int32_t a;
|
||||
std::int8_t b;
|
||||
} c;
|
||||
c.a = 1;
|
||||
return c.b == 1;
|
||||
};
|
||||
EXPECT_EQ(!!LIBIPC_ENDIAN_LIT, is_endian_little());
|
||||
EXPECT_NE(!!LIBIPC_ENDIAN_BIG, is_endian_little());
|
||||
}
|
||||
|
||||
TEST(detect_plat, fallthrough) {
|
||||
switch (0) {
|
||||
case 0:
|
||||
std::cout << "fallthrough 0\n";
|
||||
LIBIPC_FALLTHROUGH;
|
||||
case 1:
|
||||
std::cout << "fallthrough 1\n";
|
||||
LIBIPC_FALLTHROUGH;
|
||||
default:
|
||||
std::cout << "fallthrough default\n";
|
||||
break;
|
||||
}
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(detect_plat, unused) {
|
||||
LIBIPC_UNUSED int abc;
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(detect_plat, likely_unlikely) {
|
||||
int xx = sizeof(int);
|
||||
if LIBIPC_LIKELY(xx < sizeof(long long)) {
|
||||
std::cout << "sizeof(int) < sizeof(long long)\n";
|
||||
} else if LIBIPC_UNLIKELY(xx < sizeof(char)) {
|
||||
std::cout << "sizeof(int) < sizeof(char)\n";
|
||||
} else {
|
||||
std::cout << "sizeof(int) < whatever\n";
|
||||
}
|
||||
SUCCEED();
|
||||
}
|
||||
12
test/imp/test_imp_error.cpp
Normal file
12
test/imp/test_imp_error.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/imp/error.h"
|
||||
#include "libipc/imp/fmt.h"
|
||||
|
||||
TEST(error, error_code) {
|
||||
std::error_code ecode;
|
||||
EXPECT_FALSE(ecode);
|
||||
std::cout << ipc::fmt(ecode, '\n');
|
||||
}
|
||||
142
test/imp/test_imp_expected.cpp
Normal file
142
test/imp/test_imp_expected.cpp
Normal file
@ -0,0 +1,142 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/imp/expected.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class test_val {
|
||||
public:
|
||||
int val = 0;
|
||||
int dc_ = 0;
|
||||
int cc_ = 0;
|
||||
int mv_ = 0;
|
||||
|
||||
test_val() {
|
||||
dc_ += 1;
|
||||
std::printf("test_val construct.\n");
|
||||
}
|
||||
test_val(test_val const &o)
|
||||
: val(o.val) {
|
||||
cc_ = o.cc_ + 1;
|
||||
std::printf("test_val copy construct.\n");
|
||||
}
|
||||
test_val(test_val &&o) noexcept
|
||||
: val(std::exchange(o.val, 0)) {
|
||||
mv_ = o.mv_ + 1;
|
||||
std::printf("test_val move construct.\n");
|
||||
}
|
||||
~test_val() {
|
||||
std::printf("test_val destruct.\n");
|
||||
}
|
||||
|
||||
test_val &operator=(test_val &&o) noexcept {
|
||||
mv_ = o.mv_ + 1;
|
||||
val = std::exchange(o.val, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
test_val(int v)
|
||||
: val(v) {
|
||||
std::printf("test_val value initialization.\n");
|
||||
}
|
||||
|
||||
bool operator==(test_val const &rhs) const {
|
||||
return val == rhs.val;
|
||||
}
|
||||
};
|
||||
|
||||
class test_err {
|
||||
public:
|
||||
int dc_ = 0;
|
||||
int cc_ = 0;
|
||||
int mv_ = 0;
|
||||
std::int64_t val = 0;
|
||||
|
||||
test_err() {
|
||||
dc_ += 1;
|
||||
std::printf("test_err construct.\n");
|
||||
}
|
||||
test_err(test_err const &o)
|
||||
: val(o.val) {
|
||||
cc_ = o.cc_ + 1;
|
||||
std::printf("test_err copy construct.\n");
|
||||
}
|
||||
test_err(test_err &&o) noexcept
|
||||
: val(std::exchange(o.val, 0)) {
|
||||
mv_ = o.mv_ + 1;
|
||||
std::printf("test_err move construct.\n");
|
||||
}
|
||||
~test_err() {
|
||||
std::printf("test_err destruct.\n");
|
||||
}
|
||||
|
||||
test_err &operator=(test_err &&o) noexcept {
|
||||
mv_ = o.mv_ + 1;
|
||||
val = std::exchange(o.val, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
test_err(int v)
|
||||
: val(v) {
|
||||
std::printf("test_err value initialization.\n");
|
||||
}
|
||||
|
||||
bool operator==(test_err const &rhs) const {
|
||||
return val == rhs.val;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(expected, in_place) {
|
||||
ipc::expected<test_val, test_err> e1;
|
||||
EXPECT_TRUE(e1);
|
||||
EXPECT_EQ(e1.value().dc_, 1);
|
||||
EXPECT_EQ(e1.value().val, 0);
|
||||
|
||||
ipc::expected<test_val, test_err> e2 {ipc::in_place, 123};
|
||||
EXPECT_TRUE(e2);
|
||||
EXPECT_EQ(e2.value().dc_, 0);
|
||||
EXPECT_EQ(e2.value().val, 123);
|
||||
}
|
||||
|
||||
TEST(expected, unexpected) {
|
||||
ipc::expected<test_val, test_err> e1 {ipc::unexpected};
|
||||
EXPECT_FALSE(e1);
|
||||
EXPECT_EQ(e1.error().dc_, 1);
|
||||
EXPECT_EQ(e1.error().val, 0);
|
||||
|
||||
ipc::expected<test_val, test_err> e2 {ipc::unexpected, 321};
|
||||
EXPECT_FALSE(e2);
|
||||
EXPECT_EQ(e2.error().dc_, 0);
|
||||
EXPECT_EQ(e2.error().val, 321);
|
||||
}
|
||||
|
||||
TEST(expected, copy_and_move) {
|
||||
ipc::expected<test_val, test_err> e1 {ipc::in_place, 123};
|
||||
ipc::expected<test_val, test_err> e2 {e1};
|
||||
EXPECT_TRUE(e1);
|
||||
EXPECT_TRUE(e2);
|
||||
EXPECT_EQ(e1, e2);
|
||||
EXPECT_EQ(e2.value().cc_, 1);
|
||||
EXPECT_EQ(e2.value().val, 123);
|
||||
|
||||
ipc::expected<test_val, test_err> e3 {ipc::unexpected, 333};
|
||||
ipc::expected<test_val, test_err> e4 {e3};
|
||||
EXPECT_FALSE(e3);
|
||||
EXPECT_FALSE(e4);
|
||||
EXPECT_EQ(e3, e4);
|
||||
EXPECT_EQ(e4.error().cc_, 1);
|
||||
EXPECT_EQ(e4.error().val, 333);
|
||||
|
||||
ipc::expected<test_val, test_err> e5;
|
||||
e5 = e1;
|
||||
EXPECT_EQ(e1, e5);
|
||||
EXPECT_EQ(e5.value().val, 123);
|
||||
|
||||
ipc::expected<test_val, test_err> e6;
|
||||
e6 = std::move(e5);
|
||||
EXPECT_EQ(e1, e6);
|
||||
EXPECT_EQ(e5.value().val, 0);
|
||||
}
|
||||
193
test/imp/test_imp_fmt.cpp
Normal file
193
test/imp/test_imp_fmt.cpp
Normal file
@ -0,0 +1,193 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
#include <cstring>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/imp/fmt.h"
|
||||
#include "libipc/imp/byte.h"
|
||||
#include "libipc/imp/span.h"
|
||||
#include "libipc/imp/result.h"
|
||||
|
||||
TEST(fmt, spec) {
|
||||
EXPECT_STREQ(ipc::spec("hello")(123).fstr.data(), "hello");
|
||||
EXPECT_EQ(ipc::spec("hello")(123).param , 123);
|
||||
EXPECT_STREQ(ipc::spec("hello")("world").fstr.data(), "hello");
|
||||
EXPECT_STREQ(ipc::spec("hello")("world").param , "world");
|
||||
}
|
||||
|
||||
TEST(fmt, to_string) {
|
||||
std::string joined;
|
||||
ipc::fmt_context ctx(joined);
|
||||
|
||||
auto check = [&](auto &&txt, auto &&...val) {
|
||||
ctx.reset();
|
||||
EXPECT_TRUE(ipc::to_string(ctx, std::forward<decltype(val)>(val)...));
|
||||
ctx.finish();
|
||||
EXPECT_EQ(joined, std::forward<decltype(txt)>(txt));
|
||||
};
|
||||
|
||||
/// \brief string
|
||||
check("", "");
|
||||
check("%what%", "%what%");
|
||||
check(" %what%", "%what%", "10");
|
||||
check("%what% ", "%what%", "-10");
|
||||
|
||||
/// \brief character
|
||||
check("A", 'A');
|
||||
check("A", L'A');
|
||||
check("A", u'A');
|
||||
check("A", U'A');
|
||||
|
||||
/// \brief numeric
|
||||
check("123" , (signed char)123 );
|
||||
check("-65" , (signed char)-321 );
|
||||
check("123" , (unsigned char)123 );
|
||||
check("65" , (unsigned char)321 );
|
||||
check("123" , (short)123 );
|
||||
check("-321" , (short)-321 );
|
||||
check("123" , (unsigned short)123 );
|
||||
check("321" , (unsigned short)321 );
|
||||
check("-7949" , (short)123123 );
|
||||
check("6359" , (short)-321321 );
|
||||
check("57587" , (unsigned short)123123);
|
||||
check("59177" , (unsigned short)321321);
|
||||
check("123123" , 123123 );
|
||||
check("-321321" , -321321 );
|
||||
check("123123" , 123123u );
|
||||
check("321321" , 321321u );
|
||||
check("123123" , 123123ll );
|
||||
check("-321321" , -321321ll );
|
||||
check("123123" , 123123ull );
|
||||
check("321321" , 321321ull );
|
||||
check("1e0f3" , 123123, "x" );
|
||||
check("1e0f3" , 123123u, "x" );
|
||||
check("1CAAB5C3B3", 123123123123ll, "X" );
|
||||
check("1CAAB5C3B3", 123123123123ull, "X" );
|
||||
|
||||
/// \brief floating point
|
||||
check("123.123" , 123.123f, ".3");
|
||||
check("0123.12300" , 123.123, "010.5");
|
||||
check("123.123000" , 123.123l, "010.6");
|
||||
check("1.500000e+00", 1.5, "e");
|
||||
check("1.500000E+00", 1.5, "E");
|
||||
double r = 0.0;
|
||||
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, 0.0/r)); ctx.finish();
|
||||
std::cout << joined << "\n";
|
||||
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, 1.0/r)); ctx.finish();
|
||||
std::cout << joined << "\n";
|
||||
|
||||
/// \brief pointer
|
||||
check("null", nullptr);
|
||||
int *p = (int *)0x0f013a04;
|
||||
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, (void *)p)); ctx.finish();
|
||||
std::cout << joined << "\n";
|
||||
|
||||
/// \brief date and time
|
||||
auto tp = std::chrono::system_clock::now();
|
||||
auto tt = std::chrono::system_clock::to_time_t(tp);
|
||||
auto tm = *std::localtime(&tt);
|
||||
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, tm)); ctx.finish();
|
||||
std::cout << joined << "\n";
|
||||
std::string tm_str = joined;
|
||||
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, tp)); ctx.finish();
|
||||
EXPECT_EQ(tm_str, joined);
|
||||
}
|
||||
|
||||
TEST(fmt, fmt) {
|
||||
char const txt[] = "hello world.";
|
||||
|
||||
/// \brief hello world
|
||||
auto s = ipc::fmt("hello", " ", "world", ".");
|
||||
EXPECT_EQ(s, txt);
|
||||
|
||||
/// \brief chrono
|
||||
std::cout << ipc::fmt('[', std::chrono::system_clock::now(), "] ", s) << "\n";
|
||||
|
||||
/// \brief long string
|
||||
s = ipc::fmt(ipc::spec("4096")(txt));
|
||||
std::string test(4096, ' ');
|
||||
std::memcpy(&test[test.size() - sizeof(txt) + 1], txt, sizeof(txt) - 1);
|
||||
EXPECT_EQ(s, test);
|
||||
|
||||
EXPECT_EQ(ipc::fmt("", 1, "", '2', "", 3.0), "123.000000");
|
||||
char const * nc = nullptr;
|
||||
EXPECT_EQ(ipc::fmt(nc, 1, "", '2', "", 3.0), "123.000000");
|
||||
std::string empty;
|
||||
EXPECT_EQ(ipc::fmt(empty, 1, "", '2', "", 3.0), "123.000000");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class foo {};
|
||||
|
||||
bool tag_invoke(decltype(ipc::fmt_to), ipc::fmt_context &, foo arg) noexcept(false) {
|
||||
throw arg;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(fmt, throw) {
|
||||
EXPECT_THROW(std::ignore = ipc::fmt(foo{}), foo);
|
||||
}
|
||||
|
||||
TEST(fmt, byte) {
|
||||
{
|
||||
ipc::byte b1{}, b2(31);
|
||||
EXPECT_EQ(ipc::fmt(b1), "00");
|
||||
EXPECT_EQ(ipc::fmt(b2), "1f");
|
||||
EXPECT_EQ(ipc::fmt(ipc::spec("03X")(b2)), "01F");
|
||||
}
|
||||
{
|
||||
ipc::byte bs[] {31, 32, 33, 34, 35, 36, 37, 38};
|
||||
EXPECT_EQ(ipc::fmt(ipc::make_span(bs)), "1f 20 21 22 23 24 25 26");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(fmt, span) {
|
||||
EXPECT_EQ(ipc::fmt(ipc::span<int>{}), "");
|
||||
EXPECT_EQ(ipc::fmt(ipc::make_span({1, 3, 2, 4, 5, 6, 7})), "1 3 2 4 5 6 7");
|
||||
}
|
||||
|
||||
TEST(fmt, result) {
|
||||
{
|
||||
ipc::result<std::uint64_t> r1;
|
||||
EXPECT_EQ(ipc::fmt(r1), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
|
||||
ipc::result<std::uint64_t> r2(65537);
|
||||
EXPECT_EQ(ipc::fmt(r2), "succ, value = 65537");
|
||||
ipc::result<std::uint64_t> r3(0);
|
||||
EXPECT_EQ(ipc::fmt(r3), "succ, value = 0");
|
||||
}
|
||||
{
|
||||
ipc::result<int> r0;
|
||||
EXPECT_EQ(ipc::fmt(r0), ipc::fmt(ipc::result<std::uint64_t>()));
|
||||
ipc::result<int> r1 {std::error_code(-1, std::generic_category())};
|
||||
EXPECT_EQ(ipc::fmt(r1), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
|
||||
|
||||
ipc::result<void *> r2 {&r1};
|
||||
EXPECT_EQ(ipc::fmt(r2), ipc::fmt("succ, value = ", (void *)&r1));
|
||||
|
||||
int aaa {};
|
||||
ipc::result<int *> r3 {&aaa};
|
||||
EXPECT_EQ(ipc::fmt(r3), ipc::fmt("succ, value = ", (void *)&aaa));
|
||||
ipc::result<int *> r4 {nullptr};
|
||||
EXPECT_EQ(ipc::fmt(r4), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
|
||||
r4 = std::error_code(1234, std::generic_category());
|
||||
EXPECT_EQ(ipc::fmt(r4), ipc::fmt("fail, error = ", std::error_code(1234, std::generic_category())));
|
||||
ipc::result<int *> r5;
|
||||
EXPECT_EQ(ipc::fmt(r5), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
|
||||
}
|
||||
{
|
||||
ipc::result<std::int64_t> r1 {-123};
|
||||
EXPECT_EQ(ipc::fmt(r1), ipc::fmt("succ, value = ", -123));
|
||||
}
|
||||
{
|
||||
ipc::result<void> r1;
|
||||
EXPECT_EQ(ipc::fmt(r1), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
|
||||
r1 = std::error_code{};
|
||||
EXPECT_TRUE(r1);
|
||||
EXPECT_EQ(ipc::fmt(r1), ipc::fmt("succ, error = ", std::error_code()));
|
||||
}
|
||||
}
|
||||
89
test/imp/test_imp_generic.cpp
Normal file
89
test/imp/test_imp_generic.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/imp/generic.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
|
||||
TEST(generic, countof) {
|
||||
struct {
|
||||
constexpr int Size() const noexcept { return 3; }
|
||||
} sv;
|
||||
EXPECT_FALSE(ipc::detail_countof::trait_has_size<decltype(sv)>::value);
|
||||
EXPECT_TRUE (ipc::detail_countof::trait_has_Size<decltype(sv)>::value);
|
||||
|
||||
std::vector<int> vec {1, 2, 3, 4, 5};
|
||||
int arr[] {7, 6, 5, 4, 3, 2, 1};
|
||||
auto il = {9, 7, 6, 4, 3, 1, 5};
|
||||
EXPECT_EQ(ipc::countof(sv) , sv.Size());
|
||||
EXPECT_EQ(ipc::countof(vec), vec.size());
|
||||
EXPECT_EQ(ipc::countof(arr), sizeof(arr) / sizeof(arr[0]));
|
||||
EXPECT_EQ(ipc::countof(il) , il.size());
|
||||
}
|
||||
|
||||
TEST(generic, dataof) {
|
||||
struct {
|
||||
int *Data() const noexcept { return (int *)this; }
|
||||
} sv;
|
||||
EXPECT_FALSE(ipc::detail_dataof::trait_has_data<decltype(sv)>::value);
|
||||
EXPECT_TRUE (ipc::detail_dataof::trait_has_Data<decltype(sv)>::value);
|
||||
|
||||
std::vector<int> vec {1, 2, 3, 4, 5};
|
||||
int arr[] {7, 6, 5, 4, 3, 2, 1};
|
||||
auto il = {9, 7, 6, 4, 3, 1, 5};
|
||||
EXPECT_EQ(ipc::dataof(sv) , sv.Data());
|
||||
EXPECT_EQ(ipc::dataof(vec), vec.data());
|
||||
EXPECT_EQ(ipc::dataof(arr), arr);
|
||||
EXPECT_EQ(ipc::dataof(il) , il.begin());
|
||||
}
|
||||
|
||||
TEST(generic, horrible_cast) {
|
||||
struct A {
|
||||
int a_;
|
||||
} a {123};
|
||||
|
||||
struct B {
|
||||
char a_[sizeof(int)];
|
||||
} b = ipc::horrible_cast<B>(a);
|
||||
|
||||
EXPECT_EQ(b.a_[1], 0);
|
||||
EXPECT_EQ(b.a_[2], 0);
|
||||
#if LIBIPC_ENDIAN_LIT
|
||||
EXPECT_EQ(b.a_[0], 123);
|
||||
EXPECT_EQ(b.a_[3], 0);
|
||||
#else
|
||||
EXPECT_EQ(b.a_[3], 123);
|
||||
EXPECT_EQ(b.a_[0], 0);
|
||||
#endif
|
||||
|
||||
#if LIBIPC_ENDIAN_LIT
|
||||
EXPECT_EQ(ipc::horrible_cast<std::uint32_t>(0xff00'0000'0001ll), 1);
|
||||
#else
|
||||
EXPECT_EQ(ipc::horrible_cast<std::uint32_t>(0xff00'0000'0001ll), 0xff00);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(LIBIPC_CPP_17)
|
||||
TEST(generic, in_place) {
|
||||
EXPECT_TRUE((std::is_same<std::in_place_t, ipc::in_place_t>::value));
|
||||
[](ipc::in_place_t) {}(std::in_place);
|
||||
[](std::in_place_t) {}(ipc::in_place);
|
||||
}
|
||||
#endif/*LIBIPC_CPP_17*/
|
||||
|
||||
TEST(generic, copy_cvref) {
|
||||
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int , long>, long >()));
|
||||
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int & , long>, long & >()));
|
||||
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int &&, long>, long &&>()));
|
||||
|
||||
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int const , long>, long const >()));
|
||||
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int const & , long>, long const & >()));
|
||||
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int const &&, long>, long const &&>()));
|
||||
|
||||
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int volatile , long>, long volatile >()));
|
||||
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int volatile & , long>, long volatile & >()));
|
||||
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int volatile &&, long>, long volatile &&>()));
|
||||
|
||||
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int const volatile , long>, long const volatile >()));
|
||||
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int const volatile & , long>, long const volatile & >()));
|
||||
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int const volatile &&, long>, long const volatile &&>()));
|
||||
}
|
||||
46
test/imp/test_imp_log.cpp
Normal file
46
test/imp/test_imp_log.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/imp/log.h"
|
||||
|
||||
TEST(log, logger) {
|
||||
{
|
||||
LIBIPC_LOG();
|
||||
log.info("hello");
|
||||
}
|
||||
{
|
||||
LIBIPC_LOG();
|
||||
log.info("hello 2");
|
||||
}
|
||||
{
|
||||
LIBIPC_LOG();
|
||||
log.info("hello ", 3);
|
||||
}
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(log, custom) {
|
||||
struct log {
|
||||
std::string i;
|
||||
std::string e;
|
||||
} ll_data;
|
||||
auto ll = [&ll_data](auto &&ctx) {
|
||||
auto s = ipc::fmt(ctx.params);
|
||||
if (ctx.level == ipc::log::level::error) ll_data.e += s + " ";
|
||||
else
|
||||
if (ctx.level == ipc::log::level::info ) ll_data.i += s + " ";
|
||||
};
|
||||
|
||||
LIBIPC_LOG(ll);
|
||||
|
||||
log.info ("hello", " world");
|
||||
log.error("failed", ":");
|
||||
log.info ("log", '-', "pt");
|
||||
log.error("whatever");
|
||||
|
||||
EXPECT_EQ(ll_data.i, "hello world log-pt ");
|
||||
EXPECT_EQ(ll_data.e, "failed: whatever ");
|
||||
}
|
||||
67
test/imp/test_imp_result.cpp
Normal file
67
test/imp/test_imp_result.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
|
||||
#include <sstream>
|
||||
#include <cstdint>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/imp/result.h"
|
||||
|
||||
TEST(result, ok) {
|
||||
ipc::result<std::uint64_t> ret;
|
||||
EXPECT_FALSE(ret);
|
||||
EXPECT_FALSE(ret.ok());
|
||||
EXPECT_EQ(ret.value(), 0);
|
||||
|
||||
ret = {0};
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_TRUE(ret.ok());
|
||||
EXPECT_EQ(ret.value(), 0);
|
||||
|
||||
ret = ipc::result<std::uint64_t>(1234);
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_TRUE(ret.ok());
|
||||
EXPECT_EQ(ret.value(), 1234);
|
||||
|
||||
ret = std::error_code{9999, std::generic_category()};
|
||||
EXPECT_FALSE(ret);
|
||||
EXPECT_FALSE(ret.ok());
|
||||
EXPECT_EQ(ret.value(), 0);
|
||||
|
||||
ret = 4321;
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_TRUE(ret.ok());
|
||||
EXPECT_EQ(ret.value(), 4321);
|
||||
|
||||
ipc::result<void> r1;
|
||||
EXPECT_FALSE(r1);
|
||||
r1 = std::error_code{};
|
||||
EXPECT_TRUE(r1);
|
||||
r1 = {};
|
||||
EXPECT_FALSE(r1);
|
||||
r1 = std::error_code{9999, std::generic_category()};
|
||||
EXPECT_FALSE(r1);
|
||||
EXPECT_EQ(r1.error().value(), 9999);
|
||||
|
||||
ipc::result<int *> r2 {nullptr, std::error_code{4321, std::generic_category()}};
|
||||
EXPECT_NE(r2, nullptr); // ipc::result<int *>{nullptr}
|
||||
EXPECT_EQ(*r2, nullptr);
|
||||
EXPECT_FALSE(r2);
|
||||
}
|
||||
|
||||
TEST(result, compare) {
|
||||
ipc::result<std::uint64_t> r1, r2;
|
||||
EXPECT_EQ(r1, r2);
|
||||
|
||||
ipc::result<std::uint64_t> r3(0);
|
||||
EXPECT_NE(r1, r3);
|
||||
|
||||
ipc::result<std::uint64_t> r4(222222);
|
||||
EXPECT_NE(r3, r4);
|
||||
|
||||
ipc::result<std::uint64_t> r5(std::error_code{9999, std::generic_category()});
|
||||
EXPECT_NE(r4, r5);
|
||||
EXPECT_NE(r3, r5);
|
||||
|
||||
r3 = r5;
|
||||
EXPECT_EQ(r3, r5);
|
||||
}
|
||||
66
test/imp/test_imp_span.cpp
Normal file
66
test/imp/test_imp_span.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/imp/span.h"
|
||||
#include "libipc/imp/generic.h"
|
||||
#include "libipc/imp/byte.h"
|
||||
|
||||
TEST(span, to_address) {
|
||||
int *a = new int;
|
||||
EXPECT_EQ(ipc::detail_span::to_address(a), a);
|
||||
std::unique_ptr<int> b {a};
|
||||
EXPECT_EQ(ipc::detail_span::to_address(b), a);
|
||||
}
|
||||
|
||||
TEST(span, span) {
|
||||
auto test_proc = [](auto &&buf, auto &&sp) {
|
||||
EXPECT_EQ(ipc::countof(buf), sp.size());
|
||||
EXPECT_EQ(sizeof(buf[0]) * ipc::countof(buf), sp.size_bytes());
|
||||
for (std::size_t i = 0; i < sp.size(); ++i) {
|
||||
EXPECT_EQ(buf[i], sp[i]);
|
||||
}
|
||||
};
|
||||
{
|
||||
int buf[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
auto sp = ipc::make_span(buf);
|
||||
test_proc(buf, sp);
|
||||
test_proc(ipc::make_span({0, 1, 2}) , sp.first(3));
|
||||
test_proc(ipc::make_span({6, 7, 8, 9}), sp.last(4));
|
||||
test_proc(ipc::make_span({3, 4, 5, 6}), sp.subspan(3, 4));
|
||||
test_proc(ipc::make_span({3, 4, 5, 6, 7, 8, 9}), sp.subspan(3));
|
||||
}
|
||||
{
|
||||
std::vector<int> buf = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
auto sp = ipc::make_span(buf);
|
||||
ipc::span<int> sp2 {buf.begin(), buf.end()};
|
||||
EXPECT_EQ(sp, sp2);
|
||||
test_proc(buf, sp);
|
||||
test_proc(ipc::make_span({0, 1, 2}) , sp.first(3));
|
||||
test_proc(ipc::make_span({6, 7, 8, 9}), sp.last(4));
|
||||
test_proc(ipc::make_span({3, 4, 5, 6}), sp.subspan(3, 4));
|
||||
test_proc(ipc::make_span({3, 4, 5, 6, 7, 8, 9}), sp.subspan(3));
|
||||
test_proc(ipc::make_span((ipc::byte *)sp.data(), sp.size_bytes()), ipc::as_bytes(sp));
|
||||
}
|
||||
{
|
||||
std::string buf = "0123456789";
|
||||
auto sp = ipc::make_span(buf);
|
||||
// if (sp == ipc::make_span({nullptr})) {}
|
||||
test_proc(buf, sp);
|
||||
test_proc(ipc::make_span("012", 3) , sp.first(3));
|
||||
test_proc(ipc::make_span("6789", 4), sp.last(4));
|
||||
test_proc(ipc::make_span("3456", 4), sp.subspan(3, 4));
|
||||
test_proc(ipc::make_span("3456789", 7), sp.subspan(3));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(span, construct) {
|
||||
struct test_imp_span_construct {
|
||||
/* data */
|
||||
int size() const noexcept { return sizeof(this); }
|
||||
auto Data() const noexcept { return this; }
|
||||
} d1;
|
||||
ipc::span<test_imp_span_construct const> sp {d1};
|
||||
// ipc::span<int const> spp {d1};
|
||||
EXPECT_EQ(sp.size(), d1.size());
|
||||
EXPECT_EQ(sp.data(), d1.Data());
|
||||
}
|
||||
10
test/imp/test_imp_system.cpp
Normal file
10
test/imp/test_imp_system.cpp
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/imp/system.h"
|
||||
|
||||
TEST(system, conf) {
|
||||
auto ret = ipc::sys::conf(ipc::sys::info::page_size);
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_GE(ret.value(), 4096);
|
||||
}
|
||||
46
test/imp/test_imp_uninitialized.cpp
Normal file
46
test/imp/test_imp_uninitialized.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/imp/uninitialized.h"
|
||||
|
||||
TEST(uninitialized, construct) {
|
||||
struct Foo {
|
||||
int a_;
|
||||
short b_;
|
||||
char c_;
|
||||
};
|
||||
std::aligned_storage_t<sizeof(Foo)> foo;
|
||||
Foo *pfoo = ipc::construct<Foo>(&foo, 123, short{321}, '1');
|
||||
EXPECT_EQ(pfoo->a_, 123);
|
||||
EXPECT_EQ(pfoo->b_, 321);
|
||||
EXPECT_EQ(pfoo->c_, '1');
|
||||
ipc::destroy(pfoo);
|
||||
|
||||
static int bar_test_flag = 0;
|
||||
struct Bar : Foo {
|
||||
Bar(int a, short b, char c)
|
||||
: Foo{a, b, c} {
|
||||
++bar_test_flag;
|
||||
}
|
||||
~Bar() { --bar_test_flag; }
|
||||
};
|
||||
std::aligned_storage_t<sizeof(Bar)> bar;
|
||||
Bar *pbar = ipc::construct<Bar>(&bar, 123, short(321), '1');
|
||||
EXPECT_EQ(pbar->a_, 123);
|
||||
EXPECT_EQ(pbar->b_, 321);
|
||||
EXPECT_EQ(pbar->c_, '1');
|
||||
EXPECT_EQ(bar_test_flag, 1);
|
||||
ipc::destroy(pbar);
|
||||
EXPECT_EQ(bar_test_flag, 0);
|
||||
|
||||
std::aligned_storage_t<sizeof(Bar)> bars[3];
|
||||
for (auto &b : bars) {
|
||||
auto pb = ipc::construct<Bar>(&b, 321, short(123), '3');
|
||||
EXPECT_EQ(pb->a_, 321);
|
||||
EXPECT_EQ(pb->b_, 123);
|
||||
EXPECT_EQ(pb->c_, '3');
|
||||
}
|
||||
//EXPECT_EQ(bar_test_flag, ipc::countof(bars));
|
||||
ipc::destroy(reinterpret_cast<Bar(*)[3]>(&bars));
|
||||
EXPECT_EQ(bar_test_flag, 0);
|
||||
}
|
||||
32
test/mem/test_mem_block_pool.cpp
Normal file
32
test/mem/test_mem_block_pool.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "libipc/mem/block_pool.h"
|
||||
|
||||
TEST(block_pool, ctor) {
|
||||
ASSERT_TRUE ((std::is_default_constructible<ipc::mem::block_pool<1, 1>>::value));
|
||||
ASSERT_FALSE((std::is_copy_constructible<ipc::mem::block_pool<1, 1>>::value));
|
||||
ASSERT_TRUE ((std::is_move_constructible<ipc::mem::block_pool<1, 1>>::value));
|
||||
ASSERT_FALSE((std::is_copy_assignable<ipc::mem::block_pool<1, 1>>::value));
|
||||
ASSERT_FALSE((std::is_move_assignable<ipc::mem::block_pool<1, 1>>::value));
|
||||
}
|
||||
|
||||
TEST(block_pool, allocate) {
|
||||
std::vector<void *> v;
|
||||
ipc::mem::block_pool<1, 1> pool;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
v.push_back(pool.allocate());
|
||||
}
|
||||
for (void *p: v) {
|
||||
ASSERT_FALSE(nullptr == p);
|
||||
pool.deallocate(p);
|
||||
}
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
ASSERT_EQ(v[v.size() - i - 1], pool.allocate());
|
||||
}
|
||||
for (void *p: v) {
|
||||
pool.deallocate(p);
|
||||
}
|
||||
}
|
||||
103
test/mem/test_mem_bytes_allocator.cpp
Normal file
103
test/mem/test_mem_bytes_allocator.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#if defined(LIBIPC_CPP_17) && defined(__cpp_lib_memory_resource)
|
||||
# include <memory_resource>
|
||||
#endif
|
||||
|
||||
#include "libipc/mem/bytes_allocator.h"
|
||||
#include "libipc/mem/memory_resource.h"
|
||||
|
||||
TEST(bytes_allocator, ctor) {
|
||||
ipc::mem::bytes_allocator alc;
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(bytes_allocator, ctor_value_initialization) {
|
||||
ipc::mem::bytes_allocator alc{};
|
||||
auto p = alc.allocate(128);
|
||||
EXPECT_NE(p, nullptr);
|
||||
EXPECT_NO_THROW(alc.deallocate(p, 128));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class dummy_resource {
|
||||
public:
|
||||
void *allocate(std::size_t, std::size_t = 0) noexcept {
|
||||
return nullptr;
|
||||
}
|
||||
void deallocate(void *, std::size_t, std::size_t = 0) noexcept {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(bytes_allocator, memory_resource_traits) {
|
||||
EXPECT_FALSE(ipc::mem::has_allocate<void>::value);
|
||||
EXPECT_FALSE(ipc::mem::has_allocate<int>::value);
|
||||
EXPECT_FALSE(ipc::mem::has_allocate<std::vector<int>>::value);
|
||||
EXPECT_FALSE(ipc::mem::has_allocate<std::allocator<int>>::value);
|
||||
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
|
||||
EXPECT_TRUE (ipc::mem::has_allocate<std::ipc::mem::memory_resource>::value);
|
||||
EXPECT_TRUE (ipc::mem::has_allocate<std::ipc::mem::container_allocator<int>>::value);
|
||||
#endif
|
||||
|
||||
EXPECT_FALSE(ipc::mem::has_deallocate<void>::value);
|
||||
EXPECT_FALSE(ipc::mem::has_deallocate<int>::value);
|
||||
EXPECT_FALSE(ipc::mem::has_deallocate<std::vector<int>>::value);
|
||||
EXPECT_FALSE(ipc::mem::has_deallocate<std::allocator<int>>::value);
|
||||
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
|
||||
EXPECT_TRUE (ipc::mem::has_deallocate<std::ipc::mem::memory_resource>::value);
|
||||
EXPECT_FALSE(ipc::mem::has_deallocate<std::ipc::mem::container_allocator<int>>::value);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(bytes_allocator, ctor_copy_move) {
|
||||
ipc::mem::new_delete_resource mem_res;
|
||||
dummy_resource dummy_res;
|
||||
ipc::mem::bytes_allocator alc1{&mem_res}, alc2{&dummy_res};
|
||||
auto p = alc1.allocate(128);
|
||||
ASSERT_NE(p, nullptr);
|
||||
ASSERT_NO_THROW(alc1.deallocate(p, 128));
|
||||
ASSERT_EQ(alc2.allocate(128), nullptr);
|
||||
|
||||
ipc::mem::bytes_allocator alc3{alc1}, alc4{alc2}, alc5{std::move(alc1)};
|
||||
|
||||
p = alc3.allocate(128);
|
||||
ASSERT_NE(p, nullptr);
|
||||
ASSERT_NO_THROW(alc3.deallocate(p, 128));
|
||||
|
||||
ASSERT_EQ(alc4.allocate(128), nullptr);
|
||||
|
||||
p = alc5.allocate(128);
|
||||
ASSERT_NE(p, nullptr);
|
||||
ASSERT_NO_THROW(alc5.deallocate(p, 128));
|
||||
}
|
||||
|
||||
TEST(bytes_allocator, swap) {
|
||||
ipc::mem::new_delete_resource mem_res;
|
||||
dummy_resource dummy_res;
|
||||
ipc::mem::bytes_allocator alc1{&mem_res}, alc2{&dummy_res};
|
||||
alc1.swap(alc2);
|
||||
auto p = alc2.allocate(128);
|
||||
ASSERT_NE(p, nullptr);
|
||||
ASSERT_NO_THROW(alc2.deallocate(p, 128));
|
||||
ASSERT_EQ(alc1.allocate(128), nullptr);
|
||||
}
|
||||
|
||||
TEST(bytes_allocator, invalid_alloc_free) {
|
||||
ipc::mem::bytes_allocator alc1;
|
||||
EXPECT_EQ(alc1.allocate(0), nullptr);
|
||||
EXPECT_NO_THROW(alc1.deallocate(nullptr, 128));
|
||||
EXPECT_NO_THROW(alc1.deallocate(nullptr, 0));
|
||||
EXPECT_NO_THROW(alc1.deallocate(&alc1, 0));
|
||||
}
|
||||
|
||||
TEST(bytes_allocator, sizeof) {
|
||||
EXPECT_EQ(sizeof(ipc::mem::bytes_allocator), sizeof(void *) * 2);
|
||||
}
|
||||
15
test/mem/test_mem_central_cache_allocator.cpp
Normal file
15
test/mem/test_mem_central_cache_allocator.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "libipc/mem/central_cache_allocator.h"
|
||||
|
||||
TEST(central_cache_allocator, allocate) {
|
||||
auto &a = ipc::mem::central_cache_allocator();
|
||||
ASSERT_FALSE(nullptr == a.allocate(1));
|
||||
ASSERT_FALSE(nullptr == a.allocate(10));
|
||||
ASSERT_FALSE(nullptr == a.allocate(100));
|
||||
ASSERT_FALSE(nullptr == a.allocate(1000));
|
||||
ASSERT_FALSE(nullptr == a.allocate(10000));
|
||||
}
|
||||
44
test/mem/test_mem_central_cache_pool.cpp
Normal file
44
test/mem/test_mem_central_cache_pool.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "libipc/mem/central_cache_pool.h"
|
||||
|
||||
TEST(central_cache_pool, ctor) {
|
||||
ASSERT_FALSE((std::is_default_constructible<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
|
||||
ASSERT_FALSE((std::is_copy_constructible<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
|
||||
ASSERT_FALSE((std::is_move_constructible<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
|
||||
ASSERT_FALSE((std::is_copy_assignable<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
|
||||
ASSERT_FALSE((std::is_move_assignable<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
|
||||
{
|
||||
auto &pool = ipc::mem::central_cache_pool<ipc::mem::block<1024>, 1>::instance();
|
||||
ipc::mem::block<1024> *b1 = pool.aqueire();
|
||||
ASSERT_FALSE(nullptr == b1);
|
||||
EXPECT_TRUE (nullptr == b1->next);
|
||||
pool.release(b1);
|
||||
ipc::mem::block<1024> *b2 = pool.aqueire();
|
||||
EXPECT_EQ(b1, b2);
|
||||
ipc::mem::block<1024> *b3 = pool.aqueire();
|
||||
ASSERT_FALSE(nullptr == b3);
|
||||
EXPECT_TRUE (nullptr == b3->next);
|
||||
EXPECT_NE(b1, b3);
|
||||
}
|
||||
{
|
||||
auto &pool = ipc::mem::central_cache_pool<ipc::mem::block<1>, 2>::instance();
|
||||
ipc::mem::block<1> *b1 = pool.aqueire();
|
||||
ASSERT_FALSE(nullptr == b1);
|
||||
ASSERT_FALSE(nullptr == b1->next);
|
||||
EXPECT_TRUE (nullptr == b1->next->next);
|
||||
pool.release(b1);
|
||||
ipc::mem::block<1> *b2 = pool.aqueire();
|
||||
EXPECT_EQ(b1, b2);
|
||||
ipc::mem::block<1> *b3 = pool.aqueire();
|
||||
EXPECT_NE(b1, b3);
|
||||
ipc::mem::block<1> *b4 = pool.aqueire();
|
||||
ASSERT_FALSE(nullptr == b4);
|
||||
ASSERT_FALSE(nullptr == b4->next);
|
||||
EXPECT_TRUE (nullptr == b4->next->next);
|
||||
EXPECT_NE(b1, b4);
|
||||
}
|
||||
}
|
||||
165
test/mem/test_mem_memory_resource.cpp
Normal file
165
test/mem/test_mem_memory_resource.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "libipc/mem/memory_resource.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
void *test_mr(T &&mr, std::size_t bytes, std::size_t alignment) {
|
||||
auto p = std::forward<T>(mr).allocate(bytes, alignment);
|
||||
if (alignment == 0) {
|
||||
EXPECT_EQ(p, nullptr);
|
||||
} else if (p != nullptr) {
|
||||
EXPECT_EQ((std::size_t)p % alignment, 0);
|
||||
}
|
||||
std::forward<T>(mr).deallocate(p, bytes, alignment);
|
||||
return p;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(memory_resource, new_delete_resource) {
|
||||
ipc::mem::new_delete_resource mem_res;
|
||||
|
||||
EXPECT_EQ(test_mr(mem_res, 0, 0), nullptr);
|
||||
EXPECT_EQ(test_mr(mem_res, 0, 1), nullptr);
|
||||
EXPECT_EQ(test_mr(mem_res, 0, 2), nullptr);
|
||||
EXPECT_EQ(test_mr(mem_res, 0, 3), nullptr);
|
||||
EXPECT_EQ(test_mr(mem_res, 0, 8), nullptr);
|
||||
EXPECT_EQ(test_mr(mem_res, 0, 64), nullptr);
|
||||
|
||||
EXPECT_EQ(test_mr(mem_res, 1, 0), nullptr);
|
||||
EXPECT_NE(test_mr(mem_res, 1, 1), nullptr);
|
||||
EXPECT_NE(test_mr(mem_res, 1, 2), nullptr);
|
||||
EXPECT_EQ(test_mr(mem_res, 1, 3), nullptr);
|
||||
EXPECT_NE(test_mr(mem_res, 1, 8), nullptr);
|
||||
EXPECT_NE(test_mr(mem_res, 1, 64), nullptr);
|
||||
}
|
||||
|
||||
TEST(memory_resource, monotonic_buffer_resource_construct) {
|
||||
{ ipc::mem::monotonic_buffer_resource tmp; }
|
||||
ipc::mem::monotonic_buffer_resource{};
|
||||
ipc::mem::monotonic_buffer_resource{ipc::mem::bytes_allocator{}};
|
||||
ipc::mem::monotonic_buffer_resource{0};
|
||||
ipc::mem::monotonic_buffer_resource{0, ipc::mem::bytes_allocator{}};
|
||||
ipc::mem::monotonic_buffer_resource{ipc::span<ipc::byte>{}};
|
||||
ipc::mem::monotonic_buffer_resource{ipc::span<ipc::byte>{}, ipc::mem::bytes_allocator{}};
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(memory_resource, monotonic_buffer_resource_no_copy) {
|
||||
EXPECT_FALSE(std::is_copy_constructible<ipc::mem::monotonic_buffer_resource>::value);
|
||||
EXPECT_FALSE(std::is_copy_assignable<ipc::mem::monotonic_buffer_resource>::value);
|
||||
EXPECT_FALSE(std::is_move_constructible<ipc::mem::monotonic_buffer_resource>::value);
|
||||
EXPECT_FALSE(std::is_move_assignable<ipc::mem::monotonic_buffer_resource>::value);
|
||||
}
|
||||
|
||||
TEST(memory_resource, monotonic_buffer_resource_upstream_resource) {
|
||||
struct dummy_allocator {
|
||||
bool allocated = false;
|
||||
void *allocate(std::size_t, std::size_t) noexcept { allocated = true; return nullptr; }
|
||||
void deallocate(void *, std::size_t, std::size_t) noexcept {}
|
||||
} dummy;
|
||||
ipc::mem::monotonic_buffer_resource tmp{&dummy};
|
||||
ASSERT_EQ(tmp.upstream_resource().allocate(1), nullptr);
|
||||
ASSERT_TRUE(dummy.allocated);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct dummy_allocator {
|
||||
std::size_t allocated = 0;
|
||||
void *allocate(std::size_t size, std::size_t) noexcept {
|
||||
allocated += size;
|
||||
return std::malloc(size);
|
||||
}
|
||||
void deallocate(void *p, std::size_t size, std::size_t) noexcept {
|
||||
allocated -= size;
|
||||
std::free(p);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(memory_resource, monotonic_buffer_resource_allocate) {
|
||||
dummy_allocator dummy;
|
||||
{
|
||||
ipc::mem::monotonic_buffer_resource tmp{&dummy};
|
||||
ASSERT_EQ(tmp.allocate(0), nullptr);
|
||||
ASSERT_EQ(dummy.allocated, 0);
|
||||
}
|
||||
ASSERT_EQ(dummy.allocated, 0);
|
||||
{
|
||||
ipc::mem::monotonic_buffer_resource tmp{&dummy};
|
||||
std::size_t sz = 0;
|
||||
for (std::size_t i = 1; i < 1024; ++i) {
|
||||
ASSERT_NE(tmp.allocate(i), nullptr);
|
||||
sz += i;
|
||||
}
|
||||
for (std::size_t i = 1; i < 1024; ++i) {
|
||||
ASSERT_NE(tmp.allocate(1024 - i), nullptr);
|
||||
sz += 1024 - i;
|
||||
}
|
||||
ASSERT_GE(dummy.allocated, sz);
|
||||
}
|
||||
ASSERT_EQ(dummy.allocated, 0);
|
||||
}
|
||||
|
||||
TEST(memory_resource, monotonic_buffer_resource_allocate_by_buffer) {
|
||||
dummy_allocator dummy;
|
||||
std::array<ipc::byte, 4096> buffer;
|
||||
{
|
||||
ipc::mem::monotonic_buffer_resource tmp{buffer, &dummy};
|
||||
for (std::size_t i = 1; i < 64; ++i) {
|
||||
ASSERT_NE(tmp.allocate(i), nullptr);
|
||||
}
|
||||
ASSERT_EQ(dummy.allocated, 0);
|
||||
std::size_t sz = 0;
|
||||
for (std::size_t i = 1; i < 64; ++i) {
|
||||
ASSERT_NE(tmp.allocate(64 - i), nullptr);
|
||||
sz += 64 - i;
|
||||
}
|
||||
ASSERT_GT(dummy.allocated, sz);
|
||||
}
|
||||
ASSERT_EQ(dummy.allocated, 0);
|
||||
}
|
||||
|
||||
TEST(memory_resource, monotonic_buffer_resource_release) {
|
||||
dummy_allocator dummy;
|
||||
{
|
||||
ipc::mem::monotonic_buffer_resource tmp{&dummy};
|
||||
tmp.release();
|
||||
ASSERT_EQ(dummy.allocated, 0);
|
||||
ASSERT_NE(tmp.allocate(1024), nullptr);
|
||||
ASSERT_GE(dummy.allocated, 1024u);
|
||||
ASSERT_LE(dummy.allocated, 1024u * 1.5);
|
||||
tmp.release();
|
||||
ASSERT_EQ(dummy.allocated, 0);
|
||||
ASSERT_NE(tmp.allocate(1024), nullptr);
|
||||
ASSERT_GE(dummy.allocated, 1024u);
|
||||
ASSERT_LE(dummy.allocated, 1024u * 1.5);
|
||||
}
|
||||
ASSERT_EQ(dummy.allocated, 0);
|
||||
std::array<ipc::byte, 4096> buffer;
|
||||
{
|
||||
ipc::mem::monotonic_buffer_resource tmp{buffer, &dummy};
|
||||
auto *p = tmp.allocate(1024);
|
||||
ASSERT_EQ(p, buffer.data());
|
||||
ASSERT_EQ(dummy.allocated, 0);
|
||||
p = tmp.allocate(10240);
|
||||
ASSERT_NE(p, buffer.data());
|
||||
ASSERT_LE(dummy.allocated, 10240u + 1024u);
|
||||
tmp.release();
|
||||
ASSERT_EQ(dummy.allocated, 0);
|
||||
p = tmp.allocate(1024);
|
||||
ASSERT_EQ(p, buffer.data());
|
||||
ASSERT_EQ(dummy.allocated, 0);
|
||||
p = tmp.allocate(10240);
|
||||
ASSERT_NE(p, buffer.data());
|
||||
ASSERT_LE(dummy.allocated, 10240u + 1024u);
|
||||
}
|
||||
ASSERT_EQ(dummy.allocated, 0);
|
||||
}
|
||||
172
test/mem/test_mem_new.cpp
Normal file
172
test/mem/test_mem_new.cpp
Normal file
@ -0,0 +1,172 @@
|
||||
|
||||
#include <limits>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <cstddef>
|
||||
#include <thread>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/mem/new.h"
|
||||
|
||||
TEST(new, new) {
|
||||
auto p = ipc::mem::$new<int>();
|
||||
ASSERT_NE(p, nullptr);
|
||||
*p = -1;
|
||||
ASSERT_EQ(*p, -1);
|
||||
ipc::mem::$delete(p);
|
||||
}
|
||||
|
||||
TEST(new, new_value) {
|
||||
auto p = ipc::mem::$new<int>((std::numeric_limits<int>::max)());
|
||||
ASSERT_NE(p, nullptr);
|
||||
ASSERT_EQ(*p, (std::numeric_limits<int>::max)());
|
||||
ipc::mem::$delete(p);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template <std::size_t Pts, std::size_t N>
|
||||
void test_new$array() {
|
||||
std::array<void *, Pts> pts;
|
||||
using T = std::array<char, N>;
|
||||
for (int i = 0; i < (int)pts.size(); ++i) {
|
||||
auto p = ipc::mem::$new<T>();
|
||||
pts[i] = p;
|
||||
std::memset(p, i, sizeof(T));
|
||||
}
|
||||
for (int i = 0; i < (int)pts.size(); ++i) {
|
||||
T tmp;
|
||||
std::memset(&tmp, i, sizeof(T));
|
||||
ASSERT_EQ(std::memcmp(pts[i], &tmp, sizeof(T)), 0);
|
||||
ipc::mem::$delete(static_cast<T *>(pts[i]));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(new, new_array) {
|
||||
test_new$array<1000, 10>();
|
||||
test_new$array<1000, 100>();
|
||||
test_new$array<1000, 1000>();
|
||||
test_new$array<1000, 10000>();
|
||||
test_new$array<1000, 100000>();
|
||||
// test_new$array<1000, 1000000>();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
int construct_count__ = 0;
|
||||
|
||||
class Base {
|
||||
public:
|
||||
virtual ~Base() = default;
|
||||
virtual int get() const = 0;
|
||||
};
|
||||
|
||||
class Derived : public Base {
|
||||
public:
|
||||
Derived(int value) : value_(value) {
|
||||
construct_count__ = value_;
|
||||
}
|
||||
|
||||
~Derived() override {
|
||||
construct_count__ = 0;
|
||||
}
|
||||
|
||||
int get() const override { return value_; }
|
||||
|
||||
private:
|
||||
int value_;
|
||||
};
|
||||
|
||||
class Derived64K : public Derived {
|
||||
public:
|
||||
using Derived::Derived;
|
||||
|
||||
private:
|
||||
std::array<char, 65536> padding_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(new, delete_poly) {
|
||||
Base *p = ipc::mem::$new<Derived>(-1);
|
||||
ASSERT_NE(p, nullptr);
|
||||
ASSERT_EQ(p->get(), -1);
|
||||
ASSERT_EQ(construct_count__, -1);
|
||||
ipc::mem::$delete(p);
|
||||
ASSERT_EQ(construct_count__, 0);
|
||||
|
||||
ASSERT_EQ(p, ipc::mem::$new<Derived>((std::numeric_limits<int>::max)()));
|
||||
ASSERT_EQ(p->get(), (std::numeric_limits<int>::max)());
|
||||
ASSERT_EQ(construct_count__, (std::numeric_limits<int>::max)());
|
||||
ipc::mem::$delete(p);
|
||||
ASSERT_EQ(construct_count__, 0);
|
||||
}
|
||||
|
||||
TEST(new, delete_poly64k) {
|
||||
Base *p = ipc::mem::$new<Derived64K>(-1);
|
||||
ASSERT_NE(p, nullptr);
|
||||
ASSERT_EQ(p->get(), -1);
|
||||
ASSERT_EQ(construct_count__, -1);
|
||||
ipc::mem::$delete(p);
|
||||
ASSERT_EQ(construct_count__, 0);
|
||||
|
||||
Base *q = ipc::mem::$new<Derived64K>((std::numeric_limits<int>::max)());
|
||||
ASSERT_EQ(q->get(), (std::numeric_limits<int>::max)());
|
||||
ASSERT_EQ(construct_count__, (std::numeric_limits<int>::max)());
|
||||
ipc::mem::$delete(q);
|
||||
ASSERT_EQ(construct_count__, 0);
|
||||
}
|
||||
|
||||
TEST(new, delete_null) {
|
||||
Base *p = nullptr;
|
||||
ipc::mem::$delete(p);
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(new, malloc) {
|
||||
void *p = ipc::mem::$new<void>(0);
|
||||
ASSERT_EQ(p, nullptr);
|
||||
ipc::mem::$delete(p);
|
||||
p = ipc::mem::$new<void>(1024);
|
||||
ASSERT_NE(p, nullptr);
|
||||
ipc::mem::$delete(p);
|
||||
|
||||
p = ipc::mem::$new<Derived>(-1);
|
||||
ASSERT_NE(p, nullptr);
|
||||
ASSERT_EQ(((Derived *)p)->get(), -1);
|
||||
ASSERT_EQ(construct_count__, -1);
|
||||
ipc::mem::$delete(p);
|
||||
ASSERT_EQ(construct_count__, 0);
|
||||
}
|
||||
|
||||
TEST(new, multi_thread) {
|
||||
std::array<std::thread, 16> threads;
|
||||
for (auto &t : threads) {
|
||||
t = std::thread([] {
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
auto p = ipc::mem::$new<int>();
|
||||
*p = i;
|
||||
ipc::mem::$delete(p);
|
||||
}
|
||||
std::array<void *, 10000> pts;
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
auto p = ipc::mem::$new<std::array<char, 10>>();
|
||||
pts[i] = p;
|
||||
std::memset(p, i, sizeof(std::array<char, 10>));
|
||||
}
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
std::array<char, 10> tmp;
|
||||
std::memset(&tmp, i, sizeof(std::array<char, 10>));
|
||||
ASSERT_EQ(std::memcmp(pts[i], &tmp, sizeof(std::array<char, 10>)), 0);
|
||||
ipc::mem::$delete(static_cast<std::array<char, 10> *>(pts[i]));
|
||||
}
|
||||
});
|
||||
}
|
||||
for (auto &t : threads) {
|
||||
t.join();
|
||||
}
|
||||
SUCCEED();
|
||||
}
|
||||
@ -15,7 +15,7 @@
|
||||
#include "thread_pool.h"
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
#ifdef IPC_OS_LINUX_
|
||||
#ifdef LIBIPC_OS_LINUX
|
||||
#include <fcntl.h> // ::open
|
||||
#endif
|
||||
|
||||
@ -88,7 +88,7 @@ inline static thread_pool & reader() {
|
||||
return pool;
|
||||
}
|
||||
|
||||
#ifdef IPC_OS_LINUX_
|
||||
#ifdef LIBIPC_OS_LINUX
|
||||
inline bool check_exist(char const *name) noexcept {
|
||||
int fd = ::open((std::string{"/dev/shm/"} + name).c_str(), O_RDONLY);
|
||||
if (fd == -1) {
|
||||
@ -100,7 +100,7 @@ inline bool check_exist(char const *name) noexcept {
|
||||
#endif
|
||||
|
||||
inline bool expect_exist(char const *name, bool expected) noexcept {
|
||||
#ifdef IPC_OS_LINUX_
|
||||
#ifdef LIBIPC_OS_LINUX
|
||||
return ipc_ut::check_exist(name) == expected;
|
||||
#else
|
||||
return true;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user