commit 1d74fb8f7a3bc59d58171da6b492ce2ffb417917 Author: DHrpcs3 Date: Mon Dec 21 10:22:27 2015 +0200 Added utilities from rpcs3 diff --git a/common.vcxproj b/common.vcxproj new file mode 100644 index 0000000..6d853d9 --- /dev/null +++ b/common.vcxproj @@ -0,0 +1,165 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {B03AE382-DB4D-459E-A52B-C94F55381F09} + common + 8.1 + + + + StaticLibrary + true + v140 + Unicode + + + StaticLibrary + false + v140 + true + Unicode + + + StaticLibrary + true + v140 + Unicode + + + StaticLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)lib\$(Configuration)-$(Platform)\ + $(SolutionDir)tmp\$(ProjectName)-$(Configuration)-$(Platform)\ + $(ProjectDir)include\;$(VC_IncludePath);$(WindowsSDK_IncludePath) + + + $(SolutionDir)lib\$(Configuration)-$(Platform)\ + $(SolutionDir)tmp\$(ProjectName)-$(Configuration)-$(Platform)\ + $(ProjectDir)include\;$(VC_IncludePath);$(WindowsSDK_IncludePath) + + + $(SolutionDir)lib\$(Configuration)-$(Platform)\ + $(SolutionDir)tmp\$(ProjectName)-$(Configuration)-$(Platform)\ + $(ProjectDir)include\;$(VC_IncludePath);$(WindowsSDK_IncludePath) + + + $(SolutionDir)lib\$(Configuration)-$(Platform)\ + $(SolutionDir)tmp\$(ProjectName)-$(Configuration)-$(Platform)\ + $(ProjectDir)include\;$(VC_IncludePath);$(WindowsSDK_IncludePath) + + + + Level3 + Disabled + true + + + + + Level3 + Disabled + true + + + + + Level3 + MaxSpeed + true + true + true + + + true + true + + + + + Level3 + MaxSpeed + true + true + true + + + true + true + + + + + + \ No newline at end of file diff --git a/common.vcxproj.filters b/common.vcxproj.filters new file mode 100644 index 0000000..1af7ef8 --- /dev/null +++ b/common.vcxproj.filters @@ -0,0 +1,98 @@ + + + + + {fbf072d9-045e-4ff5-ba9b-fe51ae712dfd} + cpp + + + {8a3f953c-4c72-4bdd-a327-e3f173447485} + h + + + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + \ No newline at end of file diff --git a/include/Atomic.h b/include/Atomic.h new file mode 100644 index 0000000..1a6f0f8 --- /dev/null +++ b/include/Atomic.h @@ -0,0 +1,701 @@ +#pragma once +#include "types.h" +#include "GNU.h" +#include "BEType.h" + +#include + +#if defined(__GNUG__) + +template inline std::enable_if_t sync_val_compare_and_swap(volatile T* dest, T2 comp, T2 exch) +{ + return __sync_val_compare_and_swap(dest, comp, exch); +} + +template inline std::enable_if_t sync_bool_compare_and_swap(volatile T* dest, T2 comp, T2 exch) +{ + return __sync_bool_compare_and_swap(dest, comp, exch); +} + +template inline std::enable_if_t sync_lock_test_and_set(volatile T* dest, T2 value) +{ + return __sync_lock_test_and_set(dest, value); +} + +template inline std::enable_if_t sync_fetch_and_add(volatile T* dest, T2 value) +{ + return __sync_fetch_and_add(dest, value); +} + +template inline std::enable_if_t sync_fetch_and_sub(volatile T* dest, T2 value) +{ + return __sync_fetch_and_sub(dest, value); +} + +template inline std::enable_if_t sync_fetch_and_or(volatile T* dest, T2 value) +{ + return __sync_fetch_and_or(dest, value); +} + +template inline std::enable_if_t sync_fetch_and_and(volatile T* dest, T2 value) +{ + return __sync_fetch_and_and(dest, value); +} + +template inline std::enable_if_t sync_fetch_and_xor(volatile T* dest, T2 value) +{ + return __sync_fetch_and_xor(dest, value); +} + +#elif defined(_MSC_VER) + +// atomic compare and swap functions + +inline u8 sync_val_compare_and_swap(volatile u8* dest, u8 comp, u8 exch) +{ + return _InterlockedCompareExchange8((volatile char*)dest, exch, comp); +} + +inline u16 sync_val_compare_and_swap(volatile u16* dest, u16 comp, u16 exch) +{ + return _InterlockedCompareExchange16((volatile short*)dest, exch, comp); +} + +inline u32 sync_val_compare_and_swap(volatile u32* dest, u32 comp, u32 exch) +{ + return _InterlockedCompareExchange((volatile long*)dest, exch, comp); +} + +inline u64 sync_val_compare_and_swap(volatile u64* dest, u64 comp, u64 exch) +{ + return _InterlockedCompareExchange64((volatile long long*)dest, exch, comp); +} + +inline u128 sync_val_compare_and_swap(volatile u128* dest, u128 comp, u128 exch) +{ + _InterlockedCompareExchange128((volatile long long*)dest, exch.hi, exch.lo, (long long*)&comp); + return comp; +} + +inline bool sync_bool_compare_and_swap(volatile u8* dest, u8 comp, u8 exch) +{ + return (u8)_InterlockedCompareExchange8((volatile char*)dest, exch, comp) == comp; +} + +inline bool sync_bool_compare_and_swap(volatile u16* dest, u16 comp, u16 exch) +{ + return (u16)_InterlockedCompareExchange16((volatile short*)dest, exch, comp) == comp; +} + +inline bool sync_bool_compare_and_swap(volatile u32* dest, u32 comp, u32 exch) +{ + return (u32)_InterlockedCompareExchange((volatile long*)dest, exch, comp) == comp; +} + +inline bool sync_bool_compare_and_swap(volatile u64* dest, u64 comp, u64 exch) +{ + return (u64)_InterlockedCompareExchange64((volatile long long*)dest, exch, comp) == comp; +} + +inline bool sync_bool_compare_and_swap(volatile u128* dest, u128 comp, u128 exch) +{ + return _InterlockedCompareExchange128((volatile long long*)dest, exch.hi, exch.lo, (long long*)&comp) != 0; +} + +// atomic exchange functions + +inline u8 sync_lock_test_and_set(volatile u8* dest, u8 value) +{ + return _InterlockedExchange8((volatile char*)dest, value); +} + +inline u16 sync_lock_test_and_set(volatile u16* dest, u16 value) +{ + return _InterlockedExchange16((volatile short*)dest, value); +} + +inline u32 sync_lock_test_and_set(volatile u32* dest, u32 value) +{ + return _InterlockedExchange((volatile long*)dest, value); +} + +inline u64 sync_lock_test_and_set(volatile u64* dest, u64 value) +{ + return _InterlockedExchange64((volatile long long*)dest, value); +} + +inline u128 sync_lock_test_and_set(volatile u128* dest, u128 value) +{ + while (true) + { + u128 old; + old.lo = dest->lo; + old.hi = dest->hi; + + if (sync_bool_compare_and_swap(dest, old, value)) return old; + } +} + +// atomic add functions + +inline u8 sync_fetch_and_add(volatile u8* dest, u8 value) +{ + return _InterlockedExchangeAdd8((volatile char*)dest, value); +} + +inline u16 sync_fetch_and_add(volatile u16* dest, u16 value) +{ + return _InterlockedExchangeAdd16((volatile short*)dest, value); +} + +inline u32 sync_fetch_and_add(volatile u32* dest, u32 value) +{ + return _InterlockedExchangeAdd((volatile long*)dest, value); +} + +inline u64 sync_fetch_and_add(volatile u64* dest, u64 value) +{ + return _InterlockedExchangeAdd64((volatile long long*)dest, value); +} + +inline u128 sync_fetch_and_add(volatile u128* dest, u128 value) +{ + while (true) + { + u128 old; + old.lo = dest->lo; + old.hi = dest->hi; + + if (sync_bool_compare_and_swap(dest, old, old + value)) return old; + } +} + +// atomic sub functions + +inline u8 sync_fetch_and_sub(volatile u8* dest, u8 value) +{ + return _InterlockedExchangeAdd8((volatile char*)dest, -(char)value); +} + +inline u16 sync_fetch_and_sub(volatile u16* dest, u16 value) +{ + return _InterlockedExchangeAdd16((volatile short*)dest, -(short)value); +} + +inline u32 sync_fetch_and_sub(volatile u32* dest, u32 value) +{ + return _InterlockedExchangeAdd((volatile long*)dest, -(long)value); +} + +inline u64 sync_fetch_and_sub(volatile u64* dest, u64 value) +{ + return _InterlockedExchangeAdd64((volatile long long*)dest, -(long long)value); +} + +inline u128 sync_fetch_and_sub(volatile u128* dest, u128 value) +{ + while (true) + { + u128 old; + old.lo = dest->lo; + old.hi = dest->hi; + + if (sync_bool_compare_and_swap(dest, old, old - value)) return old; + } +} + +// atomic `bitwise or` functions + +inline u8 sync_fetch_and_or(volatile u8* dest, u8 value) +{ + return _InterlockedOr8((volatile char*)dest, value); +} + +inline u16 sync_fetch_and_or(volatile u16* dest, u16 value) +{ + return _InterlockedOr16((volatile short*)dest, value); +} + +inline u32 sync_fetch_and_or(volatile u32* dest, u32 value) +{ + return _InterlockedOr((volatile long*)dest, value); +} + +inline u64 sync_fetch_and_or(volatile u64* dest, u64 value) +{ + return _InterlockedOr64((volatile long long*)dest, value); +} + +inline u128 sync_fetch_and_or(volatile u128* dest, u128 value) +{ + while (true) + { + u128 old; + old.lo = dest->lo; + old.hi = dest->hi; + + if (sync_bool_compare_and_swap(dest, old, old | value)) return old; + } +} + +// atomic `bitwise and` functions + +inline u8 sync_fetch_and_and(volatile u8* dest, u8 value) +{ + return _InterlockedAnd8((volatile char*)dest, value); +} + +inline u16 sync_fetch_and_and(volatile u16* dest, u16 value) +{ + return _InterlockedAnd16((volatile short*)dest, value); +} + +inline u32 sync_fetch_and_and(volatile u32* dest, u32 value) +{ + return _InterlockedAnd((volatile long*)dest, value); +} + +inline u64 sync_fetch_and_and(volatile u64* dest, u64 value) +{ + return _InterlockedAnd64((volatile long long*)dest, value); +} + +inline u128 sync_fetch_and_and(volatile u128* dest, u128 value) +{ + while (true) + { + u128 old; + old.lo = dest->lo; + old.hi = dest->hi; + + if (sync_bool_compare_and_swap(dest, old, old & value)) return old; + } +} + +// atomic `bitwise xor` functions + +inline u8 sync_fetch_and_xor(volatile u8* dest, u8 value) +{ + return _InterlockedXor8((volatile char*)dest, value); +} + +inline u16 sync_fetch_and_xor(volatile u16* dest, u16 value) +{ + return _InterlockedXor16((volatile short*)dest, value); +} + +inline u32 sync_fetch_and_xor(volatile u32* dest, u32 value) +{ + return _InterlockedXor((volatile long*)dest, value); +} + +inline u64 sync_fetch_and_xor(volatile u64* dest, u64 value) +{ + return _InterlockedXor64((volatile long long*)dest, value); +} + +inline u128 sync_fetch_and_xor(volatile u128* dest, u128 value) +{ + while (true) + { + u128 old; + old.lo = dest->lo; + old.hi = dest->hi; + + if (sync_bool_compare_and_swap(dest, old, old ^ value)) return old; + } +} + +#endif /* _MSC_VER */ + +template struct atomic_storage +{ + static_assert(!Size, "Invalid atomic type"); +}; + +template struct atomic_storage +{ + using type = u8; +}; + +template struct atomic_storage +{ + using type = u16; +}; + +template struct atomic_storage +{ + using type = u32; +}; + +template struct atomic_storage +{ + using type = u64; +}; + +template struct atomic_storage +{ + using type = u128; +}; + +template using atomic_storage_t = typename atomic_storage::type; + +// atomic result wrapper; implements special behaviour for void result type +template struct atomic_op_result_t +{ + RT result; + + template atomic_op_result_t(T func, VT& var, Args&&... args) + : result(std::move(func(var, std::forward(args)...))) + { + } + + RT move() + { + return std::move(result); + } +}; + +// void specialization: result is the initial value of the first arg +template struct atomic_op_result_t +{ + VT result; + + template atomic_op_result_t(T func, VT& var, Args&&... args) + : result(var) + { + func(var, std::forward(args)...); + } + + VT move() + { + return std::move(result); + } +}; + +// member function specialization +template struct atomic_op_result_t +{ + RT result; + + template atomic_op_result_t(RT(CT::*func)(FArgs...), VT& var, Args&&... args) + : result(std::move((var.*func)(std::forward(args)...))) + { + } + + RT move() + { + return std::move(result); + } +}; + +// member function void specialization +template struct atomic_op_result_t +{ + VT result; + + template atomic_op_result_t(void(CT::*func)(FArgs...), VT& var, Args&&... args) + : result(var) + { + (var.*func)(std::forward(args)...); + } + + VT move() + { + return std::move(result); + } +}; + +// Atomic type with lock-free and standard layout guarantees (and appropriate limitations) +template class atomic_t +{ + using type = std::remove_cv_t; + using stype = atomic_storage_t; + using storage = atomic_storage; + + static_assert(alignof(type) <= alignof(stype), "atomic_t<> error: unexpected alignment"); + + stype m_data; + + template static inline void write_relaxed(volatile T2& data, const T2& value) + { + data = value; + } + + static inline void write_relaxed(volatile u128& data, const u128& value) + { + sync_lock_test_and_set(&data, value); + } + + template static inline T2 read_relaxed(const volatile T2& data) + { + return data; + } + + static inline u128 read_relaxed(const volatile u128& value) + { + return sync_val_compare_and_swap(const_cast(&value), u128{0}, u128{0}); + } + +public: + static inline const stype to_subtype(const type& value) + { + return reinterpret_cast(value); + } + + static inline const type from_subtype(const stype value) + { + return reinterpret_cast(value); + } + + atomic_t() = default; + + atomic_t(const atomic_t&) = delete; + + atomic_t(type value) + : m_data(to_subtype(value)) + { + } + + atomic_t& operator =(const atomic_t&) = delete; + + atomic_t& operator =(type value) + { + return write_relaxed(m_data, to_subtype(value)), *this; + } + + operator type() const volatile + { + return from_subtype(read_relaxed(m_data)); + } + + // Unsafe direct access + stype* raw_data() + { + return reinterpret_cast(&m_data); + } + + // Unsafe direct access + type& raw() + { + return reinterpret_cast(m_data); + } + + // Atomically compare data with cmp, replace with exch if equal, return previous data value anyway + type compare_and_swap(const type& cmp, const type& exch) volatile + { + return from_subtype(sync_val_compare_and_swap(&m_data, to_subtype(cmp), to_subtype(exch))); + } + + // Atomically compare data with cmp, replace with exch if equal, return true if data was replaced + bool compare_and_swap_test(const type& cmp, const type& exch) volatile + { + return sync_bool_compare_and_swap(&m_data, to_subtype(cmp), to_subtype(exch)); + } + + // Atomically replace data with exch, return previous data value + type exchange(const type& exch) volatile + { + return from_subtype(sync_lock_test_and_set(&m_data, to_subtype(exch))); + } + + // Atomically read data, possibly without memory barrier (not for 128 bit) + type load() const volatile + { + return from_subtype(read_relaxed(m_data)); + } + + // Atomically write data, possibly without memory barrier (not for 128 bit) + void store(const type& value) volatile + { + write_relaxed(m_data, to_subtype(value)); + } + + // Perform an atomic operation on data (func is either pointer to member function or callable object with a T& first arg); + // Returns the result of the callable object call or previous (old) value of the atomic variable if the return type is void + template> auto atomic_op(F func, Args&&... args) volatile -> decltype(atomic_op_result_t::result) + { + while (true) + { + // Read the old value from memory + const stype old = read_relaxed(m_data); + + // Copy the old value + stype _new = old; + + // Call atomic op for the local copy of the old value and save the return value of the function + atomic_op_result_t result(func, reinterpret_cast(_new), args...); + + // Atomically compare value with `old`, replace with `_new` and return on success + if (sync_bool_compare_and_swap(&m_data, old, _new)) return result.move(); + } + } + + // Atomic bitwise OR, returns previous data + type _or(const type& right) volatile + { + return from_subtype(sync_fetch_and_or(&m_data, to_subtype(right))); + } + + // Atomic bitwise AND, returns previous data + type _and(const type& right) volatile + { + return from_subtype(sync_fetch_and_and(&m_data, to_subtype(right))); + } + + // Atomic bitwise AND NOT (inverts right argument), returns previous data + type _and_not(const type& right) volatile + { + return from_subtype(sync_fetch_and_and(&m_data, ~to_subtype(right))); + } + + // Atomic bitwise XOR, returns previous data + type _xor(const type& right) volatile + { + return from_subtype(sync_fetch_and_xor(&m_data, to_subtype(right))); + } + + type operator |=(const type& right) volatile + { + return from_subtype(sync_fetch_and_or(&m_data, to_subtype(right)) | to_subtype(right)); + } + + type operator &=(const type& right) volatile + { + return from_subtype(sync_fetch_and_and(&m_data, to_subtype(right)) & to_subtype(right)); + } + + type operator ^=(const type& right) volatile + { + return from_subtype(sync_fetch_and_xor(&m_data, to_subtype(right)) ^ to_subtype(right)); + } +}; + +template inline std::enable_if_t operator ++(atomic_t& left) +{ + return left.from_subtype(sync_fetch_and_add(left.raw_data(), 1) + 1); +} + +template inline std::enable_if_t operator --(atomic_t& left) +{ + return left.from_subtype(sync_fetch_and_sub(left.raw_data(), 1) - 1); +} + +template inline std::enable_if_t operator ++(atomic_t& left, int) +{ + return left.from_subtype(sync_fetch_and_add(left.raw_data(), 1)); +} + +template inline std::enable_if_t operator --(atomic_t& left, int) +{ + return left.from_subtype(sync_fetch_and_sub(left.raw_data(), 1)); +} + +template inline std::enable_if_t::value, T> operator +=(atomic_t& left, const T2& right) +{ + return left.from_subtype(sync_fetch_and_add(left.raw_data(), right) + right); +} + +template inline std::enable_if_t::value, T> operator -=(atomic_t& left, const T2& right) +{ + return left.from_subtype(sync_fetch_and_sub(left.raw_data(), right) - right); +} + +template inline std::enable_if_t> operator ++(atomic_t>& left) +{ + return left.from_subtype(sync_fetch_and_add(left.raw_data(), 1) + 1); +} + +template inline std::enable_if_t> operator --(atomic_t>& left) +{ + return left.from_subtype(sync_fetch_and_sub(left.raw_data(), 1) - 1); +} + +template inline std::enable_if_t> operator ++(atomic_t>& left, int) +{ + return left.from_subtype(sync_fetch_and_add(left.raw_data(), 1)); +} + +template inline std::enable_if_t> operator --(atomic_t>& left, int) +{ + return left.from_subtype(sync_fetch_and_sub(left.raw_data(), 1)); +} + +template inline std::enable_if_t::value, nse_t> operator +=(atomic_t>& left, const T2& right) +{ + return left.from_subtype(sync_fetch_and_add(left.raw_data(), right) + right); +} + +template inline std::enable_if_t::value, nse_t> operator -=(atomic_t>& left, const T2& right) +{ + return left.from_subtype(sync_fetch_and_sub(left.raw_data(), right) - right); +} + +template inline std::enable_if_t> operator ++(atomic_t>& left) +{ + return left.atomic_op([](se_t& value) -> se_t + { + return ++value; + }); +} + +template inline std::enable_if_t> operator --(atomic_t>& left) +{ + return left.atomic_op([](se_t& value) -> se_t + { + return --value; + }); +} + +template inline std::enable_if_t> operator ++(atomic_t>& left, int) +{ + return left.atomic_op([](se_t& value) -> se_t + { + return value++; + }); +} + +template inline std::enable_if_t> operator --(atomic_t>& left, int) +{ + return left.atomic_op([](se_t& value) -> se_t + { + return value--; + }); +} + +template inline std::enable_if_t::value, se_t> operator +=(atomic_t>& left, const T2& right) +{ + return left.atomic_op([&](se_t& value) -> se_t + { + return value += right; + }); +} + +template inline std::enable_if_t::value, se_t> operator -=(atomic_t>& left, const T2& right) +{ + return left.atomic_op([&](se_t& value) -> se_t + { + return value -= right; + }); +} + +// Atomic BE Type (for PS3 virtual memory) +template using atomic_be_t = atomic_t>; + +// Atomic LE Type (for PSV virtual memory) +template using atomic_le_t = atomic_t>; + +// Algorithm for std::atomic; similar to atomic_t::atomic_op() +template> auto atomic_op(std::atomic& var, F func, Args&&... args) -> decltype(atomic_op_result_t::result) +{ + auto old = var.load(); + + while (true) + { + auto _new = old; + + atomic_op_result_t result(func, _new, args...); + + if (var.compare_exchange_strong(old, _new)) return result.move(); + } +} diff --git a/include/BEType.h b/include/BEType.h new file mode 100644 index 0000000..ac30b21 --- /dev/null +++ b/include/BEType.h @@ -0,0 +1,942 @@ +#pragma once + +#include "GNU.h" + +#ifdef _MSC_VER +#include +#else +#include +#endif + +#define IS_LE_MACHINE // only draft + +union v128 +{ + template class masked_array_t // array type accessed as (index ^ M) + { + T m_data[N]; + + public: + T& operator [](std::size_t index) + { + return m_data[index ^ M]; + } + + const T& operator [](std::size_t index) const + { + return m_data[index ^ M]; + } + + T& at(std::size_t index) + { + return (index ^ M) < N ? m_data[index ^ M] : throw std::out_of_range(__FUNCTION__); + } + + const T& at(std::size_t index) const + { + return (index ^ M) < N ? m_data[index ^ M] : throw std::out_of_range(__FUNCTION__); + } + }; + +#ifdef IS_LE_MACHINE + template using normal_array_t = masked_array_t; + template using reversed_array_t = masked_array_t; +#else + template using normal_array_t = masked_array_t; + template using reversed_array_t = masked_array_t; +#endif + + normal_array_t _u64; + normal_array_t _s64; + reversed_array_t u64r; + reversed_array_t s64r; + + normal_array_t _u32; + normal_array_t _s32; + reversed_array_t u32r; + reversed_array_t s32r; + + normal_array_t _u16; + normal_array_t _s16; + reversed_array_t u16r; + reversed_array_t s16r; + + normal_array_t _u8; + normal_array_t _s8; + reversed_array_t u8r; + reversed_array_t s8r; + + normal_array_t _f; + normal_array_t _d; + reversed_array_t fr; + reversed_array_t dr; + + __m128 vf; + __m128i vi; + __m128d vd; + + class bit_array_128 + { + u64 m_data[2]; + + public: + class bit_element + { + u64& data; + const u64 mask; + + public: + bit_element(u64& data, const u64 mask) + : data(data) + , mask(mask) + { + } + + operator bool() const + { + return (data & mask) != 0; + } + + bit_element& operator =(const bool right) + { + if (right) + { + data |= mask; + } + else + { + data &= ~mask; + } + return *this; + } + + bit_element& operator =(const bit_element& right) + { + if (right) + { + data |= mask; + } + else + { + data &= ~mask; + } + return *this; + } + }; + + // Index 0 returns the MSB and index 127 returns the LSB + bit_element operator [](u32 index) + { +#ifdef IS_LE_MACHINE + return bit_element(m_data[1 - (index >> 6)], 0x8000000000000000ull >> (index & 0x3F)); +#else + return bit_element(m_data[index >> 6], 0x8000000000000000ull >> (index & 0x3F)); +#endif + } + + // Index 0 returns the MSB and index 127 returns the LSB + bool operator [](u32 index) const + { +#ifdef IS_LE_MACHINE + return (m_data[1 - (index >> 6)] & (0x8000000000000000ull >> (index & 0x3F))) != 0; +#else + return (m_data[index >> 6] & (0x8000000000000000ull >> (index & 0x3F))) != 0; +#endif + } + + bit_element at(u32 index) + { + if (index >= 128) throw std::out_of_range(__FUNCTION__); + + return operator[](index); + } + + bool at(u32 index) const + { + if (index >= 128) throw std::out_of_range(__FUNCTION__); + + return operator[](index); + } + } + _bit; + + static v128 from64(u64 _0, u64 _1 = 0) + { + v128 ret; + ret._u64[0] = _0; + ret._u64[1] = _1; + return ret; + } + + static v128 from64r(u64 _1, u64 _0 = 0) + { + return from64(_0, _1); + } + + static v128 from32(u32 _0, u32 _1 = 0, u32 _2 = 0, u32 _3 = 0) + { + v128 ret; + ret._u32[0] = _0; + ret._u32[1] = _1; + ret._u32[2] = _2; + ret._u32[3] = _3; + return ret; + } + + static v128 from32r(u32 _3, u32 _2 = 0, u32 _1 = 0, u32 _0 = 0) + { + return from32(_0, _1, _2, _3); + } + + static v128 from32p(u32 value) + { + v128 ret; + ret.vi = _mm_set1_epi32(static_cast(value)); + return ret; + } + + static v128 from16p(u16 value) + { + v128 ret; + ret.vi = _mm_set1_epi16(static_cast(value)); + return ret; + } + + static v128 from8p(u8 value) + { + v128 ret; + ret.vi = _mm_set1_epi8(static_cast(value)); + return ret; + } + + static v128 fromBit(u32 bit) + { + v128 ret = {}; + ret._bit[bit] = true; + return ret; + } + + static v128 fromV(__m128i value) + { + v128 ret; + ret.vi = value; + return ret; + } + + static v128 fromF(__m128 value) + { + v128 ret; + ret.vf = value; + return ret; + } + + static v128 fromD(__m128d value) + { + v128 ret; + ret.vd = value; + return ret; + } + + static inline v128 add8(const v128& left, const v128& right) + { + return fromV(_mm_add_epi8(left.vi, right.vi)); + } + + static inline v128 add16(const v128& left, const v128& right) + { + return fromV(_mm_add_epi16(left.vi, right.vi)); + } + + static inline v128 add32(const v128& left, const v128& right) + { + return fromV(_mm_add_epi32(left.vi, right.vi)); + } + + static inline v128 addfs(const v128& left, const v128& right) + { + return fromF(_mm_add_ps(left.vf, right.vf)); + } + + static inline v128 addfd(const v128& left, const v128& right) + { + return fromD(_mm_add_pd(left.vd, right.vd)); + } + + static inline v128 sub8(const v128& left, const v128& right) + { + return fromV(_mm_sub_epi8(left.vi, right.vi)); + } + + static inline v128 sub16(const v128& left, const v128& right) + { + return fromV(_mm_sub_epi16(left.vi, right.vi)); + } + + static inline v128 sub32(const v128& left, const v128& right) + { + return fromV(_mm_sub_epi32(left.vi, right.vi)); + } + + static inline v128 subfs(const v128& left, const v128& right) + { + return fromF(_mm_sub_ps(left.vf, right.vf)); + } + + static inline v128 subfd(const v128& left, const v128& right) + { + return fromD(_mm_sub_pd(left.vd, right.vd)); + } + + static inline v128 maxu8(const v128& left, const v128& right) + { + return fromV(_mm_max_epu8(left.vi, right.vi)); + } + + static inline v128 minu8(const v128& left, const v128& right) + { + return fromV(_mm_min_epu8(left.vi, right.vi)); + } + + static inline v128 eq8(const v128& left, const v128& right) + { + return fromV(_mm_cmpeq_epi8(left.vi, right.vi)); + } + + static inline v128 eq16(const v128& left, const v128& right) + { + return fromV(_mm_cmpeq_epi16(left.vi, right.vi)); + } + + static inline v128 eq32(const v128& left, const v128& right) + { + return fromV(_mm_cmpeq_epi32(left.vi, right.vi)); + } + + bool operator ==(const v128& right) const + { + return _u64[0] == right._u64[0] && _u64[1] == right._u64[1]; + } + + bool operator !=(const v128& right) const + { + return _u64[0] != right._u64[0] || _u64[1] != right._u64[1]; + } + + bool is_any_1() const // check if any bit is 1 + { + return _u64[0] || _u64[1]; + } + + bool is_any_0() const // check if any bit is 0 + { + return ~_u64[0] || ~_u64[1]; + } + + // result = (~left) & (right) + static inline v128 andnot(const v128& left, const v128& right) + { + return fromV(_mm_andnot_si128(left.vi, right.vi)); + } + + void clear() + { + _u64[0] = 0; + _u64[1] = 0; + } + + std::string to_hex() const; + + std::string to_xyzw() const; + + static inline v128 byteswap(const v128 val) + { + return fromV(_mm_shuffle_epi8(val.vi, _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15))); + } +}; + +CHECK_SIZE_ALIGN(v128, 16, 16); + +inline v128 operator |(const v128& left, const v128& right) +{ + return v128::fromV(_mm_or_si128(left.vi, right.vi)); +} + +inline v128 operator &(const v128& left, const v128& right) +{ + return v128::fromV(_mm_and_si128(left.vi, right.vi)); +} + +inline v128 operator ^(const v128& left, const v128& right) +{ + return v128::fromV(_mm_xor_si128(left.vi, right.vi)); +} + +inline v128 operator ~(const v128& other) +{ + return v128::from64(~other._u64[0], ~other._u64[1]); +} + +template struct se_storage +{ + static_assert(!Size, "Bad se_storage<> type"); +}; + +template struct se_storage +{ + using type = u16; + + [[deprecated]] static constexpr u16 _swap(u16 src) // for reference + { + return (src >> 8) | (src << 8); + } + + static inline u16 swap(u16 src) + { +#if defined(__GNUG__) + return __builtin_bswap16(src); +#else + return _byteswap_ushort(src); +#endif + } + + static inline u16 to(const T& src) + { + return swap(reinterpret_cast(src)); + } + + static inline T from(u16 src) + { + const u16 result = swap(src); + return reinterpret_cast(result); + } +}; + +template struct se_storage +{ + using type = u32; + + [[deprecated]] static constexpr u32 _swap(u32 src) // for reference + { + return (src >> 24) | (src << 24) | ((src >> 8) & 0x0000ff00) | ((src << 8) & 0x00ff0000); + } + + static inline u32 swap(u32 src) + { +#if defined(__GNUG__) + return __builtin_bswap32(src); +#else + return _byteswap_ulong(src); +#endif + } + + static inline u32 to(const T& src) + { + return swap(reinterpret_cast(src)); + } + + static inline T from(u32 src) + { + const u32 result = swap(src); + return reinterpret_cast(result); + } +}; + +template struct se_storage +{ + using type = u64; + + [[deprecated]] static constexpr u64 _swap(u64 src) // for reference + { + return (src >> 56) | (src << 56) | + ((src >> 40) & 0x000000000000ff00) | + ((src >> 24) & 0x0000000000ff0000) | + ((src >> 8) & 0x00000000ff000000) | + ((src << 8) & 0x000000ff00000000) | + ((src << 24) & 0x0000ff0000000000) | + ((src << 40) & 0x00ff000000000000); + } + + static inline u64 swap(u64 src) + { +#if defined(__GNUG__) + return __builtin_bswap64(src); +#else + return _byteswap_uint64(src); +#endif + } + + static inline u64 to(const T& src) + { + return swap(reinterpret_cast(src)); + } + + static inline T from(u64 src) + { + const u64 result = swap(src); + return reinterpret_cast(result); + } +}; + +template struct se_storage +{ + using type = v128; + + static inline v128 to(const T& src) + { + return v128::byteswap(reinterpret_cast(src)); + } + + static inline T from(const v128& src) + { + const v128 result = v128::byteswap(src); + return reinterpret_cast(result); + } +}; + +template using se_storage_t = typename se_storage::type; + +template struct se_convert +{ + using type_from = std::remove_cv_t; + using type_to = std::remove_cv_t; + using stype_from = se_storage_t>; + using stype_to = se_storage_t>; + using storage_from = se_storage>; + using storage_to = se_storage>; + + static inline std::enable_if_t::value, stype_to> convert(const stype_from& data) + { + return data; + } + + static inline stype_to convert(const stype_from& data, ...) + { + return storage_to::to(storage_from::from(data)); + } +}; + +static struct se_raw_tag_t {} constexpr se_raw{}; + +template class se_t; + +// se_t with switched endianness +template class se_t +{ + using type = typename std::remove_cv::type; + using stype = se_storage_t; + using storage = se_storage; + + stype m_data; + + static_assert(!std::is_union::value && !std::is_class::value || std::is_same::value || std::is_same::value, "se_t<> error: invalid type (struct or union)"); + static_assert(!std::is_pointer::value, "se_t<> error: invalid type (pointer)"); + static_assert(!std::is_reference::value, "se_t<> error: invalid type (reference)"); + static_assert(!std::is_array::value, "se_t<> error: invalid type (array)"); + static_assert(!std::is_enum::value, "se_t<> error: invalid type (enumeration), use integral type instead"); + static_assert(alignof(type) == alignof(stype), "se_t<> error: unexpected alignment"); + + template struct bool_converter + { + static inline bool to_bool(const se_t& value) + { + return static_cast(value.value()); + } + }; + + template struct bool_converter::value>> + { + static inline bool to_bool(const se_t& value) + { + return value.m_data != 0; + } + }; + +public: + se_t() = default; + + se_t(const se_t& right) = default; + + se_t(type value) + : m_data(storage::to(value)) + { + } + + // construct directly from raw data (don't use) + constexpr se_t(const stype& raw_value, const se_raw_tag_t&) + : m_data(raw_value) + { + } + + type value() const + { + return storage::from(m_data); + } + + // access underlying raw data (don't use) + constexpr const stype& raw_data() const noexcept + { + return m_data; + } + + se_t& operator =(const se_t&) = default; + + se_t& operator =(type value) + { + return m_data = storage::to(value), *this; + } + + operator type() const + { + return storage::from(m_data); + } + + // optimization + explicit operator bool() const + { + return bool_converter::to_bool(*this); + } + + // optimization + template std::enable_if_t operator &=(const se_t& right) + { + return m_data &= right.raw_data(), *this; + } + + // optimization + template std::enable_if_t::value, se_t&> operator &=(CT right) + { + return m_data &= storage::to(right), *this; + } + + // optimization + template std::enable_if_t operator |=(const se_t& right) + { + return m_data |= right.raw_data(), *this; + } + + // optimization + template std::enable_if_t::value, se_t&> operator |=(CT right) + { + return m_data |= storage::to(right), *this; + } + + // optimization + template std::enable_if_t operator ^=(const se_t& right) + { + return m_data ^= right.raw_data(), *this; + } + + // optimization + template std::enable_if_t::value, se_t&> operator ^=(CT right) + { + return m_data ^= storage::to(right), *this; + } +}; + +// se_t with native endianness +template class se_t +{ + using type = typename std::remove_cv::type; + + type m_data; + + static_assert(!std::is_union::value && !std::is_class::value || std::is_same::value || std::is_same::value, "se_t<> error: invalid type (struct or union)"); + static_assert(!std::is_pointer::value, "se_t<> error: invalid type (pointer)"); + static_assert(!std::is_reference::value, "se_t<> error: invalid type (reference)"); + static_assert(!std::is_array::value, "se_t<> error: invalid type (array)"); + static_assert(!std::is_enum::value, "se_t<> error: invalid type (enumeration), use integral type instead"); + +public: + se_t() = default; + + se_t(const se_t&) = default; + + constexpr se_t(type value) + : m_data(value) + { + } + + type value() const + { + return m_data; + } + + se_t& operator =(const se_t& value) = default; + + se_t& operator =(type value) + { + return m_data = value, *this; + } + + operator type() const + { + return m_data; + } + + template std::enable_if_t::value, se_t&> operator &=(const CT& right) + { + return m_data &= right, *this; + } + + template std::enable_if_t::value, se_t&> operator |=(const CT& right) + { + return m_data |= right, *this; + } + + template std::enable_if_t::value, se_t&> operator ^=(const CT& right) + { + return m_data ^= right, *this; + } +}; + +// se_t with native endianness (alias) +template using nse_t = se_t; + +template inline se_t& operator +=(se_t& left, const T1& right) +{ + auto value = left.value(); + return left = (value += right); +} + +template inline se_t& operator -=(se_t& left, const T1& right) +{ + auto value = left.value(); + return left = (value -= right); +} + +template inline se_t& operator *=(se_t& left, const T1& right) +{ + auto value = left.value(); + return left = (value *= right); +} + +template inline se_t& operator /=(se_t& left, const T1& right) +{ + auto value = left.value(); + return left = (value /= right); +} + +template inline se_t& operator %=(se_t& left, const T1& right) +{ + auto value = left.value(); + return left = (value %= right); +} + +template inline se_t& operator <<=(se_t& left, const T1& right) +{ + auto value = left.value(); + return left = (value <<= right); +} + +template inline se_t& operator >>=(se_t& left, const T1& right) +{ + auto value = left.value(); + return left = (value >>= right); +} + +template inline se_t operator ++(se_t& left, int) +{ + auto value = left.value(); + auto result = value++; + left = value; + return result; +} + +template inline se_t operator --(se_t& left, int) +{ + auto value = left.value(); + auto result = value--; + left = value; + return result; +} + +template inline se_t& operator ++(se_t& right) +{ + auto value = right.value(); + return right = ++value; +} + +template inline se_t& operator --(se_t& right) +{ + auto value = right.value(); + return right = --value; +} + +// optimization +template inline std::enable_if_t operator ==(const se_t& left, const se_t& right) +{ + return left.raw_data() == right.raw_data(); +} + +// optimization +template inline std::enable_if_t= sizeof(T2), bool> operator ==(const se_t& left, T2 right) +{ + return left.raw_data() == se_storage::to(right); +} + +// optimization +template inline std::enable_if_t operator ==(T1 left, const se_t& right) +{ + return se_storage::to(left) == right.raw_data(); +} + +// optimization +template inline std::enable_if_t operator !=(const se_t& left, const se_t& right) +{ + return left.raw_data() != right.raw_data(); +} + +// optimization +template inline std::enable_if_t= sizeof(T2), bool> operator !=(const se_t& left, T2 right) +{ + return left.raw_data() != se_storage::to(right); +} + +// optimization +template inline std::enable_if_t operator !=(T1 left, const se_t& right) +{ + return se_storage::to(left) != right.raw_data(); +} + +// optimization +template inline std::enable_if_t= 4, se_t> operator &(const se_t& left, const se_t& right) +{ + return{ left.raw_data() & right.raw_data(), se_raw }; +} + +// optimization +template inline std::enable_if_t= sizeof(T2) && sizeof(T1) >= 4, se_t> operator &(const se_t& left, T2 right) +{ + return{ left.raw_data() & se_storage::to(right), se_raw }; +} + +// optimization +template inline std::enable_if_t= 4, se_t> operator &(T1 left, const se_t& right) +{ + return{ se_storage::to(left) & right.raw_data(), se_raw }; +} + +// optimization +template inline std::enable_if_t= 4, se_t> operator |(const se_t& left, const se_t& right) +{ + return{ left.raw_data() | right.raw_data(), se_raw }; +} + +// optimization +template inline std::enable_if_t= sizeof(T2) && sizeof(T1) >= 4, se_t> operator |(const se_t& left, T2 right) +{ + return{ left.raw_data() | se_storage::to(right), se_raw }; +} + +// optimization +template inline std::enable_if_t= 4, se_t> operator |(T1 left, const se_t& right) +{ + return{ se_storage::to(left) | right.raw_data(), se_raw }; +} + +// optimization +template inline std::enable_if_t= 4, se_t> operator ^(const se_t& left, const se_t& right) +{ + return{ left.raw_data() ^ right.raw_data(), se_raw }; +} + +// optimization +template inline std::enable_if_t= sizeof(T2) && sizeof(T1) >= 4, se_t> operator ^(const se_t& left, T2 right) +{ + return{ left.raw_data() ^ se_storage::to(right), se_raw }; +} + +// optimization +template inline std::enable_if_t= 4, se_t> operator ^(T1 left, const se_t& right) +{ + return{ se_storage::to(left) ^ right.raw_data(), se_raw }; +} + +// optimization +template inline std::enable_if_t= 4, se_t> operator ~(const se_t& right) +{ + return{ ~right.raw_data(), se_raw }; +} + +#ifdef IS_LE_MACHINE +template using be_t = se_t; +template using le_t = se_t; +#else +template using be_t = se_t; +template using le_t = se_t; +#endif + + +template struct to_se +{ + using type = typename std::conditional::value || std::is_enum::value, se_t, T>::type; +}; + +template struct to_se::value>> // move const qualifier +{ + using type = const typename to_se::type; +}; + +template struct to_se::value && !std::is_const::value>> // move volatile qualifier +{ + using type = volatile typename to_se::type; +}; + +template struct to_se +{ + using type = typename to_se::type[]; +}; + +template struct to_se +{ + using type = typename to_se::type[N]; +}; + +template struct to_se { using type = se_t; }; +template struct to_se { using type = se_t; }; +template struct to_se { using type = bool; }; +template struct to_se { using type = char; }; +template struct to_se { using type = u8; }; +template struct to_se { using type = s8; }; + +#ifdef IS_LE_MACHINE +template using to_be_t = typename to_se::type; +template using to_le_t = typename to_se::type; +#else +template using to_be_t = typename to_se::type; +template using to_le_t = typename to_se::type; +#endif + + +template struct to_ne +{ + using type = T; +}; + +template struct to_ne> +{ + using type = typename std::remove_cv::type; +}; + +template struct to_ne::value>> // move const qualifier +{ + using type = const typename to_ne::type; +}; + +template struct to_ne::value && !std::is_const::value>> // move volatile qualifier +{ + using type = volatile typename to_ne::type; +}; + +template struct to_ne +{ + using type = typename to_ne::type[]; +}; + +template struct to_ne +{ + using type = typename to_ne::type[N]; +}; + +// restore native endianness for T: returns T for be_t or le_t, T otherwise +template using to_ne_t = typename to_ne::type; diff --git a/include/BitField.h b/include/BitField.h new file mode 100644 index 0000000..7c7ad07 --- /dev/null +++ b/include/BitField.h @@ -0,0 +1,140 @@ +#pragma once + +// BitField access helper class (N bits from I position), intended to be put in union +template class bf_t +{ + // Checks + static_assert(I < sizeof(T) * 8, "bf_t<> error: I out of bounds"); + static_assert(N < sizeof(T) * 8, "bf_t<> error: N out of bounds"); + static_assert(I + N <= sizeof(T) * 8, "bf_t<> error: values out of bounds"); + + // Underlying data type + using type = typename std::remove_cv::type; + + // Underlying value type (native endianness) + using vtype = typename to_ne::type; + + // Mask of size N + constexpr static vtype s_mask = (static_cast(1) << N) - 1; + + // Underlying data member + type m_data; + + // Conversion operator helper (uses SFINAE) + template struct converter {}; + + template struct converter::value>> + { + // Load unsigned value + static inline T2 convert(const type& data) + { + return (data >> I) & s_mask; + } + }; + + template struct converter::value>> + { + // Load signed value (sign-extended) + static inline T2 convert(const type& data) + { + return data << (sizeof(T) * 8 - I - N) >> (sizeof(T) * 8 - N); + } + }; + +public: + // Assignment operator (store bitfield value) + bf_t& operator =(vtype value) + { + m_data = (m_data & ~(s_mask << I)) | (value & s_mask) << I; + return *this; + } + + // Conversion operator (load bitfield value) + operator vtype() const + { + return converter::convert(m_data); + } + + // Get raw data with mask applied + type unshifted() const + { + return (m_data & (s_mask << I)); + } + + // Optimized bool conversion + explicit operator bool() const + { + return unshifted() != 0; + } + + // Postfix increment operator + vtype operator ++(int) + { + vtype result = *this; + *this = result + 1; + return result; + } + + // Prefix increment operator + bf_t& operator ++() + { + return *this = *this + 1; + } + + // Postfix decrement operator + vtype operator --(int) + { + vtype result = *this; + *this = result - 1; + return result; + } + + // Prefix decrement operator + bf_t& operator --() + { + return *this = *this - 1; + } + + // Addition assignment operator + bf_t& operator +=(vtype right) + { + return *this = *this + right; + } + + // Subtraction assignment operator + bf_t& operator -=(vtype right) + { + return *this = *this - right; + } + + // Multiplication assignment operator + bf_t& operator *=(vtype right) + { + return *this = *this * right; + } + + // Bitwise AND assignment operator + bf_t& operator &=(vtype right) + { + m_data &= (right & s_mask) << I; + return *this; + } + + // Bitwise OR assignment operator + bf_t& operator |=(vtype right) + { + m_data |= (right & s_mask) << I; + return *this; + } + + // Bitwise XOR assignment operator + bf_t& operator ^=(vtype right) + { + m_data ^= (right & s_mask) << I; + return *this; + } +}; + +template using bf_be_t = bf_t, I, N>; + +template using bf_le_t = bf_t, I, N>; diff --git a/include/File.h b/include/File.h new file mode 100644 index 0000000..89fc5f2 --- /dev/null +++ b/include/File.h @@ -0,0 +1,303 @@ +#pragma once +#include "types.h" +#include +#include +#include + +enum class fsm : u32 // file seek mode +{ + begin, + cur, + end, +}; + +namespace fom // file open mode +{ + enum : u32 + { + read = 1 << 0, // enable reading + write = 1 << 1, // enable writing + append = 1 << 2, // enable appending (always write to the end of file) + create = 1 << 3, // create file if it doesn't exist + trunc = 1 << 4, // clear opened file if it's not empty + excl = 1 << 5, // failure if the file already exists (used with `create`) + + rewrite = write | create | trunc, // write + create + trunc + }; +}; + +enum class fse : u32 // filesystem (file or dir) error +{ + ok, // no error + invalid_arguments, +}; + +namespace fs +{ + thread_local extern fse g_tls_error; + + struct stat_t + { + bool is_directory; + bool is_writable; + u64 size; + s64 atime; + s64 mtime; + s64 ctime; + }; + + // Get file information + bool stat(const std::string& path, stat_t& info); + + // Check whether a file or a directory exists (not recommended, use is_file() or is_dir() instead) + bool exists(const std::string& path); + + // Check whether the file exists and is NOT a directory + bool is_file(const std::string& file); + + // Check whether the directory exists and is NOT a file + bool is_dir(const std::string& dir); + + // Delete empty directory + bool remove_dir(const std::string& dir); + + // Create directory + bool create_dir(const std::string& dir); + + // Create directories + bool create_path(const std::string& path); + + // Rename (move) file or directory + bool rename(const std::string& from, const std::string& to); + + // Copy file contents + bool copy_file(const std::string& from, const std::string& to, bool overwrite); + + // Delete file + bool remove_file(const std::string& file); + + // Change file size (possibly appending zeros) + bool truncate_file(const std::string& file, u64 length); + + class file final + { + using handle_type = std::intptr_t; + + constexpr static handle_type null = -1; + + handle_type m_fd = null; + + friend class file_ptr; + + public: + file() = default; + + explicit file(const std::string& filename, u32 mode = fom::read) + { + open(filename, mode); + } + + file(file&& other) + : m_fd(other.m_fd) + { + other.m_fd = null; + } + + file& operator =(file&& right) + { + std::swap(m_fd, right.m_fd); + return *this; + } + + ~file(); + + // Check whether the handle is valid (opened file) + bool is_opened() const + { + return m_fd != null; + } + + // Check whether the handle is valid (opened file) + explicit operator bool() const + { + return is_opened(); + } + + // Open specified file with specified mode + bool open(const std::string& filename, u32 mode = fom::read); + + // Change file size (possibly appending zero bytes) + bool trunc(u64 size) const; + + // Get file information + bool stat(stat_t& info) const; + + // Close the file explicitly (destructor automatically closes the file) + bool close(); + + // Read the data from the file and return the amount of data written in buffer + u64 read(void* buffer, u64 count) const; + + // Write the data to the file and return the amount of data actually written + u64 write(const void* buffer, u64 count) const; + + // Move file pointer + u64 seek(s64 offset, fsm seek_mode = fsm::begin) const; + + // Get file size + u64 size() const; + + // Write std::string + const file& operator <<(const std::string& str) const + { + CHECK_ASSERTION(write(str.data(), str.size()) == str.size()); + return *this; + } + + // Write POD + template + std::enable_if_t::value && !std::is_pointer::value, const file&> operator <<(const T& data) const + { + CHECK_ASSERTION(write(std::addressof(data), sizeof(T)) == sizeof(T)); + return *this; + } + + // Write POD std::vector + template + std::enable_if_t::value && !std::is_pointer::value, const file&> operator <<(const std::vector& vec) const + { + CHECK_ASSERTION(write(vec.data(), vec.size() * sizeof(T)) == vec.size() * sizeof(T)); + return *this; + } + + // Read std::string + bool read(std::string& str) const + { + return read(&str[0], str.size()) == str.size(); + } + + // Read POD + template + std::enable_if_t::value && !std::is_pointer::value, bool> read(T& data) const + { + return read(&data, sizeof(T)) == sizeof(T); + } + + // Read POD std::vector + template + std::enable_if_t::value && !std::is_pointer::value, bool> read(std::vector& vec) const + { + return read(vec.data(), sizeof(T) * vec.size()) == sizeof(T) * vec.size(); + } + + // Convert to std::string + operator std::string() const + { + std::string result; + result.resize(size() - seek(0, fsm::cur)); + CHECK_ASSERTION(read(result)); + return result; + } + }; + + class file_ptr final + { + char* m_ptr = nullptr; + u64 m_size; + + public: + file_ptr() = default; + + file_ptr(file_ptr&& right) + : m_ptr(right.m_ptr) + , m_size(right.m_size) + { + right.m_ptr = 0; + } + + file_ptr& operator =(file_ptr&& right) + { + std::swap(m_ptr, right.m_ptr); + std::swap(m_size, right.m_size); + return *this; + } + + file_ptr(const file& f) + { + reset(f); + } + + ~file_ptr() + { + reset(); + } + + // Open file mapping + void reset(const file& f); + + // Close file mapping + void reset(); + + // Get pointer + operator char*() const + { + return m_ptr; + } + }; + + class dir final + { + std::unique_ptr m_path; + std::intptr_t m_dd; // handle (aux) + + public: + dir() = default; + + explicit dir(const std::string& dirname) + { + open(dirname); + } + + dir(dir&& other) + : m_dd(other.m_dd) + , m_path(std::move(other.m_path)) + { + } + + dir& operator =(dir&& right) + { + std::swap(m_dd, right.m_dd); + std::swap(m_path, right.m_path); + return *this; + } + + ~dir(); + + // Check whether the handle is valid (opened directory) + bool is_opened() const + { + return m_path.operator bool(); + } + + // Check whether the handle is valid (opened directory) + explicit operator bool() const + { + return is_opened(); + } + + // Open specified directory + bool open(const std::string& dirname); + + // Close the directory explicitly (destructor automatically closes the directory) + bool close(); + + // Get next directory entry (UTF-8 name and file stat) + bool read(std::string& name, stat_t& info); + }; + + // Get configuration directory + std::string get_config_dir(); + + // Get executable directory + std::string get_executable_dir(); +} diff --git a/include/GNU.h b/include/GNU.h new file mode 100644 index 0000000..3459b92 --- /dev/null +++ b/include/GNU.h @@ -0,0 +1,425 @@ +#pragma once + +#include +#include + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +#define thread_local __declspec(thread) +#elif __APPLE__ +#define thread_local __thread +#endif + +#if defined(_MSC_VER) +#define never_inline __declspec(noinline) +#else +#define never_inline __attribute__((noinline)) +#endif + +#if defined(_MSC_VER) +#define safe_buffers __declspec(safebuffers) +#else +#define safe_buffers +#endif + +#if defined(_MSC_VER) +#define force_inline __forceinline +#else +#define force_inline __attribute__((always_inline)) +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +#define alignas(x) _CRT_ALIGN(x) +#endif + +#if defined(__GNUG__) + +#include +#include + +#ifndef __APPLE__ +#include +#endif + +#define _fpclass(x) std::fpclassify(x) +#define INFINITE 0xFFFFFFFF + +#ifdef __APPLE__ + +// XXX only supports a single timer +#define TIMER_ABSTIME -1 +/* The opengroup spec isn't clear on the mapping from REALTIME to CALENDAR + being appropriate or not. + http://pubs.opengroup.org/onlinepubs/009695299/basedefs/time.h.html */ +#define CLOCK_REALTIME 1 // #define CALENDAR_CLOCK 1 from mach/clock_types.h +#define CLOCK_MONOTONIC 0 // #define SYSTEM_CLOCK 0 + +typedef int clockid_t; + +/* the mach kernel uses struct mach_timespec, so struct timespec + is loaded from for compatability */ +// struct timespec { time_t tv_sec; long tv_nsec; }; + +int clock_gettime(clockid_t clk_id, struct timespec *tp); + +#endif /* __APPLE__ */ +#endif /* __GNUG__ */ + +#if defined(_MSC_VER) + +// Unsigned 128-bit integer implementation +struct alignas(16) u128 +{ + std::uint64_t lo, hi; + + u128() = default; + + u128(const u128&) = default; + + u128(std::uint64_t l) + : lo(l) + , hi(0) + { + } + + u128 operator +(const u128& r) const + { + u128 value; + _addcarry_u64(_addcarry_u64(0, r.lo, lo, &value.lo), r.hi, hi, &value.hi); + return value; + } + + friend u128 operator +(const u128& l, std::uint64_t r) + { + u128 value; + _addcarry_u64(_addcarry_u64(0, r, l.lo, &value.lo), l.hi, 0, &value.hi); + return value; + } + + friend u128 operator +(std::uint64_t l, const u128& r) + { + u128 value; + _addcarry_u64(_addcarry_u64(0, r.lo, l, &value.lo), 0, r.hi, &value.hi); + return value; + } + + u128 operator -(const u128& r) const + { + u128 value; + _subborrow_u64(_subborrow_u64(0, r.lo, lo, &value.lo), r.hi, hi, &value.hi); + return value; + } + + friend u128 operator -(const u128& l, std::uint64_t r) + { + u128 value; + _subborrow_u64(_subborrow_u64(0, r, l.lo, &value.lo), 0, l.hi, &value.hi); + return value; + } + + friend u128 operator -(std::uint64_t l, const u128& r) + { + u128 value; + _subborrow_u64(_subborrow_u64(0, r.lo, l, &value.lo), r.hi, 0, &value.hi); + return value; + } + + u128 operator +() const + { + return *this; + } + + u128 operator -() const + { + u128 value; + _subborrow_u64(_subborrow_u64(0, lo, 0, &value.lo), hi, 0, &value.hi); + return value; + } + + u128& operator ++() + { + _addcarry_u64(_addcarry_u64(0, 1, lo, &lo), 0, hi, &hi); + return *this; + } + + u128 operator ++(int) + { + u128 value = *this; + _addcarry_u64(_addcarry_u64(0, 1, lo, &lo), 0, hi, &hi); + return value; + } + + u128& operator --() + { + _subborrow_u64(_subborrow_u64(0, 1, lo, &lo), 0, hi, &hi); + return *this; + } + + u128 operator --(int) + { + u128 value = *this; + _subborrow_u64(_subborrow_u64(0, 1, lo, &lo), 0, hi, &hi); + return value; + } + + u128 operator ~() const + { + u128 value; + value.lo = ~lo; + value.hi = ~hi; + return value; + } + + u128 operator &(const u128& r) const + { + u128 value; + value.lo = lo & r.lo; + value.hi = hi & r.hi; + return value; + } + + u128 operator |(const u128& r) const + { + u128 value; + value.lo = lo | r.lo; + value.hi = hi | r.hi; + return value; + } + + u128 operator ^(const u128& r) const + { + u128 value; + value.lo = lo ^ r.lo; + value.hi = hi ^ r.hi; + return value; + } + + u128& operator +=(const u128& r) + { + _addcarry_u64(_addcarry_u64(0, r.lo, lo, &lo), r.hi, hi, &hi); + return *this; + } + + u128& operator +=(uint64_t r) + { + _addcarry_u64(_addcarry_u64(0, r, lo, &lo), 0, hi, &hi); + return *this; + } + + u128& operator &=(const u128& r) + { + lo &= r.lo; + hi &= r.hi; + return *this; + } + + u128& operator |=(const u128& r) + { + lo |= r.lo; + hi |= r.hi; + return *this; + } + + u128& operator ^=(const u128& r) + { + lo ^= r.lo; + hi ^= r.hi; + return *this; + } +}; +#endif + +inline std::uint32_t cntlz32(std::uint32_t arg) +{ +#if defined(_MSC_VER) + unsigned long res; + return _BitScanReverse(&res, arg) ? res ^ 31 : 32; +#else + return arg ? __builtin_clzll(arg) - 32 : 32; +#endif +} + +inline std::uint64_t cntlz64(std::uint64_t arg) +{ +#if defined(_MSC_VER) + unsigned long res; + return _BitScanReverse64(&res, arg) ? res ^ 63 : 64; +#else + return arg ? __builtin_clzll(arg) : 64; +#endif +} + +// compare 16 packed unsigned bytes (greater than) +inline __m128i sse_cmpgt_epu8(__m128i A, __m128i B) +{ + // (A xor 0x80) > (B xor 0x80) + const auto sign = _mm_set1_epi32(0x80808080); + return _mm_cmpgt_epi8(_mm_xor_si128(A, sign), _mm_xor_si128(B, sign)); +} + +inline __m128i sse_cmpgt_epu16(__m128i A, __m128i B) +{ + const auto sign = _mm_set1_epi32(0x80008000); + return _mm_cmpgt_epi16(_mm_xor_si128(A, sign), _mm_xor_si128(B, sign)); +} + +inline __m128i sse_cmpgt_epu32(__m128i A, __m128i B) +{ + const auto sign = _mm_set1_epi32(0x80000000); + return _mm_cmpgt_epi32(_mm_xor_si128(A, sign), _mm_xor_si128(B, sign)); +} + +inline __m128 sse_exp2_ps(__m128 A) +{ + const auto x0 = _mm_max_ps(_mm_min_ps(A, _mm_set1_ps(127.4999961f)), _mm_set1_ps(-127.4999961f)); + const auto x1 = _mm_add_ps(x0, _mm_set1_ps(0.5f)); + const auto x2 = _mm_sub_epi32(_mm_cvtps_epi32(x1), _mm_and_si128(_mm_castps_si128(_mm_cmpnlt_ps(_mm_setzero_ps(), x1)), _mm_set1_epi32(1))); + const auto x3 = _mm_sub_ps(x0, _mm_cvtepi32_ps(x2)); + const auto x4 = _mm_mul_ps(x3, x3); + const auto x5 = _mm_mul_ps(x3, _mm_add_ps(_mm_mul_ps(_mm_add_ps(_mm_mul_ps(x4, _mm_set1_ps(0.023093347705f)), _mm_set1_ps(20.20206567f)), x4), _mm_set1_ps(1513.906801f))); + const auto x6 = _mm_mul_ps(x5, _mm_rcp_ps(_mm_sub_ps(_mm_add_ps(_mm_mul_ps(_mm_set1_ps(233.1842117f), x4), _mm_set1_ps(4368.211667f)), x5))); + return _mm_mul_ps(_mm_add_ps(_mm_add_ps(x6, x6), _mm_set1_ps(1.0f)), _mm_castsi128_ps(_mm_slli_epi32(_mm_add_epi32(x2, _mm_set1_epi32(127)), 23))); +} + +inline __m128 sse_log2_ps(__m128 A) +{ + const auto _1 = _mm_set1_ps(1.0f); + const auto _c = _mm_set1_ps(1.442695040f); + const auto x0 = _mm_max_ps(A, _mm_castsi128_ps(_mm_set1_epi32(0x00800000))); + const auto x1 = _mm_or_ps(_mm_and_ps(x0, _mm_castsi128_ps(_mm_set1_epi32(0x807fffff))), _1); + const auto x2 = _mm_rcp_ps(_mm_add_ps(x1, _1)); + const auto x3 = _mm_mul_ps(_mm_sub_ps(x1, _1), x2); + const auto x4 = _mm_add_ps(x3, x3); + const auto x5 = _mm_mul_ps(x4, x4); + const auto x6 = _mm_add_ps(_mm_mul_ps(_mm_add_ps(_mm_mul_ps(_mm_set1_ps(-0.7895802789f), x5), _mm_set1_ps(16.38666457f)), x5), _mm_set1_ps(-64.1409953f)); + const auto x7 = _mm_rcp_ps(_mm_add_ps(_mm_mul_ps(_mm_add_ps(_mm_mul_ps(_mm_set1_ps(-35.67227983f), x5), _mm_set1_ps(312.0937664f)), x5), _mm_set1_ps(-769.6919436f))); + const auto x8 = _mm_cvtepi32_ps(_mm_sub_epi32(_mm_srli_epi32(_mm_castps_si128(x0), 23), _mm_set1_epi32(127))); + return _mm_add_ps(_mm_mul_ps(_mm_mul_ps(_mm_mul_ps(_mm_mul_ps(x5, x6), x7), x4), _c), _mm_add_ps(_mm_mul_ps(x4, _c), x8)); +} + +#define CHECK_SIZE(type, size) static_assert(sizeof(type) == size, "Invalid " #type " type size") +#define CHECK_ALIGN(type, align) static_assert(alignof(type) == align, "Invalid " #type " type alignment") +#define CHECK_MAX_SIZE(type, size) static_assert(sizeof(type) <= size, #type " type size is too big") +#define CHECK_SIZE_ALIGN(type, size, align) CHECK_SIZE(type, size); CHECK_ALIGN(type, align) +#define CHECK_ASCENDING(constexpr_array) static_assert(::is_ascending(constexpr_array), #constexpr_array " is not sorted in ascending order") + +#ifndef _MSC_VER +using u128 = __uint128_t; +#endif + +CHECK_SIZE_ALIGN(u128, 16, 16); + +#include "types.h" + +// bool type replacement for PS3/PSV +class b8 +{ + std::uint8_t m_value; + +public: + b8(const bool value) + : m_value(value) + { + } + + operator bool() const //template::value>> operator T() const + { + return m_value != 0; + } +}; + +CHECK_SIZE_ALIGN(b8, 1, 1); + +template::value>> inline T align(const T& value, u64 align) +{ + return static_cast((value + (align - 1)) & ~(align - 1)); +} + +// copy null-terminated string from std::string to char array with truncation +template inline void strcpy_trunc(char(&dst)[N], const std::string& src) +{ + const std::size_t count = src.size() >= N ? N - 1 : src.size(); + std::memcpy(dst, src.c_str(), count); + dst[count] = '\0'; +} + +// copy null-terminated string from char array to char array with truncation +template inline void strcpy_trunc(char(&dst)[N], const char(&src)[N2]) +{ + const std::size_t count = N2 >= N ? N - 1 : N2; + std::memcpy(dst, src, count); + dst[count] = '\0'; +} + +// returns true if all array elements are unique and sorted in ascending order +template constexpr bool is_ascending(const T(&array)[N], std::size_t from = 0) +{ + return from >= N - 1 ? true : array[from] < array[from + 1] ? is_ascending(array, from + 1) : false; +} + +// get (first) array element equal to `value` or nullptr if not found +template constexpr const T* static_search(const T(&array)[N], const T2& value, std::size_t from = 0) +{ + return from >= N ? nullptr : array[from] == value ? array + from : static_search(array, value, from + 1); +} + +// bool wrapper for restricting bool result conversions +struct explicit_bool_t +{ + const bool value; + + constexpr explicit_bool_t(bool value) + : value(value) + { + } + + explicit constexpr operator bool() const + { + return value; + } +}; + +template struct triplet_t +{ + T1 first; + T2 second; + T3 third; + + constexpr bool operator ==(const T1& right) const + { + return first == right; + } +}; + +// return 32 bit sizeof() to avoid widening/narrowing conversions with size_t +#define sizeof32(type) static_cast(sizeof(type)) + +// return 32 bit alignof() to avoid widening/narrowing conversions with size_t +#define alignof32(type) static_cast(alignof(type)) + +// return 32 bit .size() for container +template inline auto size32(const T& container) -> decltype(static_cast(container.size())) +{ + const auto size = container.size(); + return size >= 0 && size <= UINT32_MAX ? static_cast(size) : throw std::length_error(__FUNCTION__); +} + +// return 32 bit size for an array +template constexpr u32 size32(const T(&)[Size]) +{ + return Size >= 0 && Size <= UINT32_MAX ? static_cast(Size) : throw std::length_error(__FUNCTION__); +} + + +#define WRAP_EXPR(expr) [&]{ return expr; } +#define COPY_EXPR(expr) [=]{ return expr; } +#define PURE_EXPR(expr) [] { return expr; } +#define EXCEPTION(text, ...) fmt::exception(__FILE__, __LINE__, __FUNCTION__, text, ##__VA_ARGS__) +#define VM_CAST(value) vm::impl_cast(value, __FILE__, __LINE__, __FUNCTION__) +#define IS_INTEGRAL(t) (std::is_integral::value || std::is_same, u128>::value) +#define IS_INTEGER(t) (std::is_integral::value || std::is_enum::value || std::is_same, u128>::value) +#define IS_BINARY_COMPARABLE(t1, t2) (IS_INTEGER(t1) && IS_INTEGER(t2) && sizeof(t1) == sizeof(t2)) +#define CHECK_ASSERTION(expr) if (expr) {} else throw EXCEPTION("Assertion failed: " #expr) +#define CHECK_SUCCESS(expr) if (s32 _r = (expr)) throw EXCEPTION(#expr " failed (0x%x)", _r) + +// Some forward declarations for the ID manager +template struct id_traits; diff --git a/include/Interval.h b/include/Interval.h new file mode 100644 index 0000000..bffa46d --- /dev/null +++ b/include/Interval.h @@ -0,0 +1,33 @@ +#pragma once +#include + +template struct range_t +{ + T1 _min; // first value + T2 _max; // second value +}; + +template constexpr range_t, std::decay_t> make_range(T1&& _min, T2&& _max) +{ + return{ std::forward(_min), std::forward(_max) }; +} + +template constexpr bool operator <(const range_t& range, const T& value) +{ + return range._min < value && range._max < value; +} + +template constexpr bool operator <(const T& value, const range_t& range) +{ + return value < range._min && value < range._max; +} + +template constexpr bool operator ==(const range_t& range, const T& value) +{ + return !(value < range._min) && !(range._max < value); +} + +template constexpr bool operator ==(const T& value, const range_t& range) +{ + return !(value < range._min) && !(range._max < value); +} diff --git a/include/Log.h b/include/Log.h new file mode 100644 index 0000000..a5f6de1 --- /dev/null +++ b/include/Log.h @@ -0,0 +1,140 @@ +#pragma once +#include "types.h" +#include "MTRingbuffer.h" +#include "GNU.h" + +#include +#include +#include + +//#define BUFFERED_LOGGING 1 + +//first parameter is of type Log::LogType and text is of type std::string + +#define LOG_SUCCESS(logType, text, ...) log_message(logType, Log::Severity::Success, text, ##__VA_ARGS__) +#define LOG_NOTICE(logType, text, ...) log_message(logType, Log::Severity::Notice, text, ##__VA_ARGS__) +#define LOG_WARNING(logType, text, ...) log_message(logType, Log::Severity::Warning, text, ##__VA_ARGS__) +#define LOG_ERROR(logType, text, ...) log_message(logType, Log::Severity::Error, text, ##__VA_ARGS__) + +namespace Log +{ + const unsigned int MAX_LOG_BUFFER_LENGTH = 1024*1024; + const unsigned int gBuffSize = 1000; + + enum LogType : u32 + { + GENERAL = 0, + LOADER, + MEMORY, + RSX, + HLE, + PPU, + SPU, + ARMv7, + TTY, + }; + + + struct LogTypeName + { + LogType mType; + std::string mName; + }; + + //well I'd love make_array() but alas manually counting is not the end of the world + static const std::array gTypeNameTable = { { + { GENERAL, "G: " }, + { LOADER, "LDR: " }, + { MEMORY, "MEM: " }, + { RSX, "RSX: " }, + { HLE, "HLE: " }, + { PPU, "PPU: " }, + { SPU, "SPU: " }, + { ARMv7, "ARM: " }, + { TTY, "TTY: " } + } }; + + enum class Severity : u32 + { + Notice = 0, + Warning, + Success, + Error, + }; + + struct LogMessage + { + using size_type = u32; + LogType mType; + Severity mServerity; + std::string mText; + + u32 size() const; + void serialize(char *output) const; + static LogMessage deserialize(char *input, u32* size_out=nullptr); + }; + + struct LogListener + { + virtual ~LogListener() {}; + virtual void log(const LogMessage &msg) = 0; + }; + + struct LogChannel + { + LogChannel(); + LogChannel(const std::string& name); + LogChannel(LogChannel& other) = delete; + void log(const LogMessage &msg); + void addListener(std::shared_ptr listener); + void removeListener(std::shared_ptr listener); + std::string name; + private: + bool mEnabled; + Severity mLogLevel; + std::mutex mListenerLock; + std::set> mListeners; + }; + + struct LogManager + { + LogManager(); + ~LogManager(); + static LogManager& getInstance(); + LogChannel& getChannel(LogType type); + void log(LogMessage msg); + void addListener(std::shared_ptr listener); + void removeListener(std::shared_ptr listener); +#ifdef BUFFERED_LOGGING + void consumeLog(); +#endif + private: +#ifdef BUFFERED_LOGGING + MTRingbuffer mBuffer; + std::condition_variable mBufferReady; + std::mutex mStatusMut; + std::atomic mExiting; + std::thread mLogConsumer; +#endif + std::array::value> mChannels; + //std::array mChannels; //TODO: use this once Microsoft sorts their shit out + }; +} + +static struct { inline operator Log::LogType() { return Log::LogType::GENERAL; } } GENERAL; +static struct { inline operator Log::LogType() { return Log::LogType::LOADER; } } LOADER; +static struct { inline operator Log::LogType() { return Log::LogType::MEMORY; } } MEMORY; +static struct { inline operator Log::LogType() { return Log::LogType::RSX; } } RSX; +static struct { inline operator Log::LogType() { return Log::LogType::HLE; } } HLE; +static struct { inline operator Log::LogType() { return Log::LogType::PPU; } } PPU; +static struct { inline operator Log::LogType() { return Log::LogType::SPU; } } SPU; +static struct { inline operator Log::LogType() { return Log::LogType::ARMv7; } } ARMv7; +static struct { inline operator Log::LogType() { return Log::LogType::TTY; } } TTY; + +void log_message(Log::LogType type, Log::Severity sev, const char* text); +void log_message(Log::LogType type, Log::Severity sev, std::string text); + +template never_inline void log_message(Log::LogType type, Log::Severity sev, const char* fmt, Args... args) +{ + log_message(type, sev, fmt::format(fmt, fmt::do_unveil(args)...)); +} diff --git a/include/MTRingbuffer.h b/include/MTRingbuffer.h new file mode 100644 index 0000000..71eb064 --- /dev/null +++ b/include/MTRingbuffer.h @@ -0,0 +1,158 @@ +#pragma once +#include +#include +#include + +//Simple non-resizable FIFO Ringbuffer that can be simultaneously be read from and written to +//if we ever get to use boost please replace this with boost::circular_buffer, there's no reason +//why we would have to keep this amateur attempt at such a fundamental data-structure around +template< typename T, unsigned int MAX_MTRINGBUFFER_BUFFER_SIZE> +class MTRingbuffer{ + std::array mBuffer; + //this is a recursive mutex because the get methods lock it but the only + //way to be sure that they do not block is to check the size and the only + //way to check the size and use get atomically is to lock this mutex, + //so it goes: + //lock get mutex-->check size-->call get-->lock get mutex-->unlock get mutex-->return from get-->unlock get mutex + std::recursive_mutex mMutGet; + std::mutex mMutPut; + + size_t mGet; + size_t mPut; + size_t moveGet(size_t by = 1){ return (mGet + by) % MAX_MTRINGBUFFER_BUFFER_SIZE; } + size_t movePut(size_t by = 1){ return (mPut + by) % MAX_MTRINGBUFFER_BUFFER_SIZE; } +public: + MTRingbuffer() : mGet(0), mPut(0){} + + //blocks until there's something to get, so check "spaceLeft()" if you want to avoid blocking + //also lock the get mutex around the spaceLeft() check and the pop if you want to avoid racing + T pop() + { + std::lock_guard lock(mMutGet); + while (mGet == mPut) + { + //wait until there's actually something to get + //throwing an exception might be better, blocking here is a little awkward + std::this_thread::sleep_for(std::chrono::milliseconds(1)); // hack + } + size_t ret = mGet; + mGet = moveGet(); + return mBuffer[ret]; + } + + //blocks if the buffer is full until there's enough room + void push(T &putEle) + { + std::lock_guard lock(mMutPut); + while (movePut() == mGet) + { + //if this is reached a lot it's time to increase the buffer size + //or implement dynamic re-sizing + std::this_thread::sleep_for(std::chrono::milliseconds(1)); // hack + } + mBuffer[mPut] = std::forward(putEle); + mPut = movePut(); + } + + bool empty() + { + return mGet == mPut; + } + + //returns the amount of free places, this is the amount of actual free spaces-1 + //since mGet==mPut signals an empty buffer we can't actually use the last free + //space, so we shouldn't report it as free. + size_t spaceLeft() //apparently free() is a macro definition in msvc in some conditions + { + if (mGet < mPut) + { + return mBuffer.size() - (mPut - mGet) - 1; + } + else if (mGet > mPut) + { + return mGet - mPut - 1; + } + else + { + return mBuffer.size() - 1; + } + } + + size_t size() + { + //the magic -1 is the same magic 1 that is explained in the spaceLeft() function + return mBuffer.size() - spaceLeft() - 1; + } + + //takes random access iterator to T + template + void pushRange(IteratorType from, IteratorType until) + { + std::lock_guard lock(mMutPut); + size_t length = until - from; + + //if whatever we're trying to store is greater than the entire buffer the following loop will be infinite + assert(mBuffer.size() > length); + while (spaceLeft() < length) + { + //if this is reached a lot it's time to increase the buffer size + //or implement dynamic re-sizing + std::this_thread::sleep_for(std::chrono::milliseconds(1)); // hack + } + if (mPut + length <= mBuffer.size()) + { + std::copy(from, until, mBuffer.begin() + mPut); + } + else + { + size_t tillEnd = mBuffer.size() - mPut; + std::copy(from, from + tillEnd, mBuffer.begin() + mPut); + std::copy(from + tillEnd, until, mBuffer.begin()); + } + mPut = movePut(length); + + } + + //takes output iterator to T + template + void popN(IteratorType output, size_t n) + { + std::lock_guard lock(mMutGet); + //make sure we're not trying to retrieve more than is in + assert(n <= size()); + peekN(output, n); + mGet = moveGet(n); + } + + //takes output iterator to T + template + void peekN(IteratorType output, size_t n) + { + size_t lGet = mGet; + if (lGet + n <= mBuffer.size()) + { + std::copy_n(mBuffer.begin() + lGet, n, output); + } + else + { + auto next = std::copy(mBuffer.begin() + lGet, mBuffer.end(), output); + std::copy_n(mBuffer.begin(), n - (mBuffer.size() - lGet), next); + } + } + + //well this is just asking for trouble + //but the comment above the declaration of mMutGet explains why it's there + //if there's a better way please remove this + void lockGet() + { + mMutGet.lock(); + } + + //well this is just asking for trouble + //but the comment above the declaration of mMutGet explains why it's there + //if there's a better way please remove this + void unlockGet() + { + mMutGet.unlock(); + } +}; diff --git a/include/Semaphore.h b/include/Semaphore.h new file mode 100644 index 0000000..7cb12c4 --- /dev/null +++ b/include/Semaphore.h @@ -0,0 +1,39 @@ +#pragma once +#include "Atomic.h" +#include + +class semaphore_t +{ + // semaphore mutex + std::mutex m_mutex; + + // semaphore condition variable + std::condition_variable m_cv; + + struct sync_var_t + { + u32 value; // current semaphore value + u32 waiters; // current amount of waiters + }; + + // current semaphore value + atomic_t m_var; + +public: + // max semaphore value + const u32 max_value; + + semaphore_t(u32 max_value = 1, u32 value = 0) + : m_var(sync_var_t{ value, 0 }) + , max_value(max_value) + { + } + + bool try_wait(); + + bool try_post(); + + void wait(); + + bool post_and_wait(); +}; \ No newline at end of file diff --git a/include/SharedMutex.h b/include/SharedMutex.h new file mode 100644 index 0000000..2b09938 --- /dev/null +++ b/include/SharedMutex.h @@ -0,0 +1,141 @@ +#pragma once +#include "types.h" +#include +#include +#include +#include + +//! An attempt to create effective implementation of "shared mutex", lock-free in optimistic case. +//! All locking and unlocking may be done by single LOCK XADD or LOCK CMPXCHG instructions. +//! MSVC implementation of std::shared_timed_mutex seems suboptimal. +//! std::shared_mutex is not available until C++17. +class shared_mutex final +{ + enum : u32 + { + SM_WRITER_LOCK = 1u << 31, // Exclusive lock flag, must be MSB + SM_WRITER_QUEUE = 1u << 30, // Flag set if m_wq_size != 0 + SM_READER_QUEUE = 1u << 29, // Flag set if m_rq_size != 0 + + SM_READER_COUNT = SM_READER_QUEUE - 1, // Valid reader count bit mask + SM_READER_MAX = 1u << 24, // Max reader count + }; + + std::atomic m_ctrl{}; // Control atomic variable: reader count | SM_* flags + std::thread::id m_owner{}; // Current exclusive owner (TODO: implement only for debug mode?) + + std::mutex m_mutex; + + u32 m_rq_size{}; // Reader queue size (threads waiting on m_rcv) + u32 m_wq_size{}; // Writer queue size (threads waiting on m_wcv+m_ocv) + + std::condition_variable m_rcv; // Reader queue + std::condition_variable m_wcv; // Writer queue + std::condition_variable m_ocv; // For current exclusive owner + + static bool op_lock_shared(u32& ctrl) + { + // Check writer flags and reader limit + return (ctrl & ~SM_READER_QUEUE) < SM_READER_MAX ? ctrl++, true : false; + } + + static bool op_lock_excl(u32& ctrl) + { + // Test and set writer lock + return (ctrl & SM_WRITER_LOCK) == 0 ? ctrl |= SM_WRITER_LOCK, true : false; + } + + void impl_lock_shared(u32 old_ctrl); + void impl_unlock_shared(u32 new_ctrl); + void impl_lock_excl(u32 ctrl); + void impl_unlock_excl(u32 ctrl); + +public: + shared_mutex() = default; + + // Lock in shared mode + void lock_shared() + { + const u32 old_ctrl = m_ctrl++; + + // Check flags and reader limit + if (old_ctrl >= SM_READER_MAX) + { + impl_lock_shared(old_ctrl); + } + } + + // Try to lock in shared mode + bool try_lock_shared() + { + return atomic_op(m_ctrl, [](u32& ctrl) + { + // Check flags and reader limit + return ctrl < SM_READER_MAX ? ctrl++, true : false; + }); + } + + // Unlock in shared mode + void unlock_shared() + { + const u32 new_ctrl = --m_ctrl; + + // Check if notification required + if (new_ctrl >= SM_READER_MAX) + { + impl_unlock_shared(new_ctrl); + } + } + + // Lock exclusively + void lock() + { + u32 value = 0; + + if (!m_ctrl.compare_exchange_strong(value, SM_WRITER_LOCK)) + { + impl_lock_excl(value); + } + } + + // Try to lock exclusively + bool try_lock() + { + u32 value = 0; + + return m_ctrl.compare_exchange_strong(value, SM_WRITER_LOCK); + } + + // Unlock exclusively + void unlock() + { + const u32 value = m_ctrl.fetch_add(SM_WRITER_LOCK); + + // Check if notification required + if (value != SM_WRITER_LOCK) + { + impl_unlock_excl(value); + } + } +}; + +//! Simplified shared (reader) lock implementation, similar to std::lock_guard. +//! std::shared_lock may be used instead if necessary. +class reader_lock final +{ + shared_mutex& m_mutex; + +public: + reader_lock(const reader_lock&) = delete; + + reader_lock(shared_mutex& mutex) + : m_mutex(mutex) + { + m_mutex.lock_shared(); + } + + ~reader_lock() + { + m_mutex.unlock_shared(); + } +}; diff --git a/include/SleepQueue.h b/include/SleepQueue.h new file mode 100644 index 0000000..1de522f --- /dev/null +++ b/include/SleepQueue.h @@ -0,0 +1,52 @@ +#pragma once +#include +#include + +struct sleep_entry_t : std::enable_shared_from_this +{ + virtual void sleep() = 0; + virtual void awake() = 0; +}; + +using sleep_queue_t = std::deque>; + +static struct defer_sleep_t {} const defer_sleep{}; + +// automatic object handling a thread entry in the sleep queue +class sleep_queue_entry_t final +{ + sleep_entry_t& m_thread; + sleep_queue_t& m_queue; + + void add_entry(); + void remove_entry(); + bool find() const; + +public: + // add specified thread to the sleep queue + sleep_queue_entry_t(sleep_entry_t& entry, sleep_queue_t& queue); + + // don't add specified thread to the sleep queue + sleep_queue_entry_t(sleep_entry_t& entry, sleep_queue_t& queue, const defer_sleep_t&); + + // removes specified thread from the sleep queue if added + ~sleep_queue_entry_t(); + + // add thread to the sleep queue + void enter() + { + add_entry(); + } + + // remove thread from the sleep queue + void leave() + { + remove_entry(); + } + + // check whether the thread exists in the sleep queue + explicit operator bool() const + { + return find(); + } +}; diff --git a/include/StrFmt.h b/include/StrFmt.h new file mode 100644 index 0000000..78e27fe --- /dev/null +++ b/include/StrFmt.h @@ -0,0 +1,354 @@ +#pragma once +#include +#include +#include "types.h" +#include "BEType.h" +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +#define snprintf _snprintf +#endif + +namespace fmt +{ + //struct empty_t{}; + + //extern const std::string placeholder; + + template + std::string AfterLast(const std::string& source, T searchstr) + { + size_t search_pos = source.rfind(searchstr); + search_pos = search_pos == std::string::npos ? 0 : search_pos; + return source.substr(search_pos); + } + + template + std::string BeforeLast(const std::string& source, T searchstr) + { + size_t search_pos = source.rfind(searchstr); + search_pos = search_pos == std::string::npos ? 0 : search_pos; + return source.substr(0, search_pos); + } + + template + std::string AfterFirst(const std::string& source, T searchstr) + { + size_t search_pos = source.find(searchstr); + search_pos = search_pos == std::string::npos ? 0 : search_pos; + return source.substr(search_pos); + } + + template + std::string BeforeFirst(const std::string& source, T searchstr) + { + size_t search_pos = source.find(searchstr); + search_pos = search_pos == std::string::npos ? 0 : search_pos; + return source.substr(0, search_pos); + } + + // write `fmt` from `pos` to the first occurence of `fmt::placeholder` to + // the stream `os`. Then write `arg` to to the stream. If there's no + // `fmt::placeholder` after `pos` everything in `fmt` after pos is written + // to `os`. Then `arg` is written to `os` after appending a space character + //template + //empty_t write(const std::string &fmt, std::ostream &os, std::string::size_type &pos, T &&arg) + //{ + // std::string::size_type ins = fmt.find(placeholder, pos); + + // if (ins == std::string::npos) + // { + // os.write(fmt.data() + pos, fmt.size() - pos); + // os << ' ' << arg; + + // pos = fmt.size(); + // } + // else + // { + // os.write(fmt.data() + pos, ins - pos); + // os << arg; + + // pos = ins + placeholder.size(); + // } + // return{}; + //} + + // typesafe version of a sprintf-like function. Returns the printed to + // string. To mark positions where the arguments are supposed to be + // inserted use `fmt::placeholder`. If there's not enough placeholders + // the rest of the arguments are appended at the end, seperated by spaces + //template + //std::string SFormat(const std::string &fmt, Args&& ... parameters) + //{ + // std::ostringstream os; + // std::string::size_type pos = 0; + // std::initializer_list { write(fmt, os, pos, parameters)... }; + + // if (!fmt.empty()) + // { + // os.write(fmt.data() + pos, fmt.size() - pos); + // } + + // std::string result = os.str(); + // return result; + //} + + std::string replace_first(const std::string& src, const std::string& from, const std::string& to); + std::string replace_all(const std::string &src, const std::string& from, const std::string& to); + + template + std::string replace_all(std::string src, const std::pair(&list)[list_size]) + { + for (size_t pos = 0; pos < src.length(); ++pos) + { + for (size_t i = 0; i < list_size; ++i) + { + const size_t comp_length = list[i].first.length(); + + if (src.length() - pos < comp_length) + continue; + + if (src.substr(pos, comp_length) == list[i].first) + { + src = (pos ? src.substr(0, pos) + list[i].second : list[i].second) + src.substr(pos + comp_length); + pos += list[i].second.length() - 1; + break; + } + } + } + + return src; + } + + template + std::string replace_all(std::string src, const std::pair>(&list)[list_size]) + { + for (size_t pos = 0; pos < src.length(); ++pos) + { + for (size_t i = 0; i < list_size; ++i) + { + const size_t comp_length = list[i].first.length(); + + if (src.length() - pos < comp_length) + continue; + + if (src.substr(pos, comp_length) == list[i].first) + { + src = (pos ? src.substr(0, pos) + list[i].second() : list[i].second()) + src.substr(pos + comp_length); + pos += list[i].second().length() - 1; + break; + } + } + } + + return src; + } + + std::string to_hex(u64 value, u64 count = 1); + std::string to_udec(u64 value); + std::string to_sdec(s64 value); + + template::value> struct unveil + { + using result_type = T; + + force_inline static result_type get_value(const T& arg) + { + return arg; + } + }; + + template<> struct unveil + { + using result_type = const char*; + + force_inline static result_type get_value(const char* arg) + { + return arg; + } + }; + + template struct unveil + { + using result_type = const char*; + + force_inline static result_type get_value(const char(&arg)[N]) + { + return arg; + } + }; + + template<> struct unveil + { + using result_type = const char*; + + force_inline static result_type get_value(const std::string& arg) + { + return arg.c_str(); + } + }; + + template struct unveil + { + using result_type = std::underlying_type_t; + + force_inline static result_type get_value(const T& arg) + { + return static_cast(arg); + } + }; + + template struct unveil, false> + { + using result_type = typename unveil::result_type; + + force_inline static result_type get_value(const se_t& arg) + { + return unveil::get_value(arg); + } + }; + + template + force_inline typename unveil::result_type do_unveil(const T& arg) + { + return unveil::get_value(arg); + } + + // Formatting function with special functionality: + // + // std::string is forced to .c_str() + // be_t<> is forced to .value() (fmt::do_unveil reverts byte order automatically) + // + // External specializations for fmt::do_unveil (can be found in another headers): + // vm::ptr, vm::bptr, ... (fmt::do_unveil) (vm_ptr.h) (with appropriate address type, using .addr() can be avoided) + // vm::ref, vm::bref, ... (fmt::do_unveil) (vm_ref.h) + // + template safe_buffers std::string format(const char* fmt, Args... args) + { + // fixed stack buffer for the first attempt + std::array fixed_buf; + + // possibly dynamically allocated buffer for the second attempt + std::unique_ptr buf; + + // pointer to the current buffer + char* buf_addr = fixed_buf.data(); + + for (std::size_t buf_size = fixed_buf.size();;) + { +#ifndef _MSC_VER +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-security" +#endif + const std::size_t len = std::snprintf(buf_addr, buf_size, fmt, do_unveil(args)...); +#ifndef _MSC_VER +#pragma GCC diagnostic pop +#endif + if (len > INT_MAX) + { + throw std::runtime_error("std::snprintf() failed"); + } + + if (len < buf_size) + { + return{ buf_addr, len }; + } + + buf.reset(buf_addr = new char[buf_size = len + 1]); + } + } + + struct exception : public std::exception + { + std::unique_ptr message; + + template never_inline safe_buffers exception(const char* file, int line, const char* func, const char* text, Args... args) noexcept + { + const std::string data = format(text, args...) + format("\n(in file %s:%d, in function %s)", file, line, func); + + message.reset(new char[data.size() + 1]); + + std::memcpy(message.get(), data.c_str(), data.size() + 1); + } + + exception(const exception& other) noexcept + { + const std::size_t size = std::strlen(other.message.get()); + + message.reset(new char[size + 1]); + + std::memcpy(message.get(), other.message.get(), size + 1); + } + + virtual const char* what() const noexcept override + { + return message.get(); + } + }; + + //TODO: remove this after every snippet that uses it is gone + //WARNING: not fully compatible with CmpNoCase from wxString + int CmpNoCase(const std::string& a, const std::string& b); + + //TODO: remove this after every snippet that uses it is gone + //WARNING: not fully compatible with Replace from wxString + void Replace(std::string &str, const std::string &searchterm, const std::string& replaceterm); + + std::vector rSplit(const std::string& source, const std::string& delim); + + std::vector split(const std::string& source, std::initializer_list separators, bool is_skip_empty = true); + std::string trim(const std::string& source, const std::string& values = " \t"); + + template + std::string merge(const T& source, const std::string& separator) + { + if (!source.size()) + { + return{}; + } + + std::string result; + + auto it = source.begin(); + auto end = source.end(); + for (--end; it != end; ++it) + { + result += *it + separator; + } + + return result + source.back(); + } + + template + std::string merge(std::initializer_list sources, const std::string& separator) + { + if (!sources.size()) + { + return{}; + } + + std::string result; + bool first = true; + + for (auto &v : sources) + { + if (first) + { + result = fmt::merge(v, separator); + first = false; + } + else + { + result += separator + fmt::merge(v, separator); + } + } + + return result; + } + + std::string tolower(std::string source); + std::string toupper(std::string source); + std::string escape(std::string source); +} diff --git a/include/Timer.h b/include/Timer.h new file mode 100644 index 0000000..e217a15 --- /dev/null +++ b/include/Timer.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +class Timer +{ +private: + bool m_stopped; + std::chrono::high_resolution_clock::time_point m_start; + std::chrono::high_resolution_clock::time_point m_end; + +public: + Timer() : m_stopped(false) + { + } + + void Start() + { + m_stopped = false; + m_start = std::chrono::high_resolution_clock::now(); + } + + void Stop() + { + m_stopped = true; + m_end = std::chrono::high_resolution_clock::now(); + } + + double GetElapsedTimeInSec() const + { + return double(GetElapsedTimeInMicroSec()) / 1000000.0; + } + + double GetElapsedTimeInMilliSec() const + { + return double(GetElapsedTimeInMicroSec()) / 1000.0; + } + + std::uint64_t GetElapsedTimeInMicroSec() const + { + std::chrono::high_resolution_clock::time_point now = m_stopped ? m_end : std::chrono::high_resolution_clock::now(); + + return std::chrono::duration_cast(now - m_start).count(); + } + + std::uint64_t GetElapsedTimeInNanoSec() const + { + std::chrono::high_resolution_clock::time_point now = m_stopped ? m_end : std::chrono::high_resolution_clock::now(); + + return std::chrono::duration_cast(now - m_start).count(); + } +}; diff --git a/include/VirtualMemory.h b/include/VirtualMemory.h new file mode 100644 index 0000000..434fc8a --- /dev/null +++ b/include/VirtualMemory.h @@ -0,0 +1,23 @@ +#pragma once +#include + +namespace memory_helper +{ + /** + * Reserve size bytes of virtual memory and returns it. + * The memory should be commited before usage. + */ + void* reserve_memory(size_t size); + + /** + * Commit page_size bytes of virtual memory starting at pointer. + * That is, bake reserved memory with physical memory. + * pointer should belong to a range of reserved memory. + */ + void commit_page_memory(void* pointer, size_t page_size); + + /** + * Free memory alloced via reserve_memory. + */ + void free_reserved_memory(void* pointer, size_t size); +} \ No newline at end of file diff --git a/include/config_context.h b/include/config_context.h new file mode 100644 index 0000000..dd328d1 --- /dev/null +++ b/include/config_context.h @@ -0,0 +1,163 @@ +#pragma once +#include +#include +#include "convert.h" + +class config_context_t +{ +public: + class entry_base; + +protected: + class group + { + group* m_parent; + config_context_t* m_cfg; + std::string m_name; + std::vector> m_entries; + + void init(); + + public: + std::unordered_map entries; + + group(config_context_t* cfg, const std::string& name); + group(group* parent, const std::string& name); + void set_parent(config_context_t* cfg); + + std::string name() const; + std::string full_name() const; + + template + void add_entry(const std::string& name, const T& def_value) + { + m_entries.emplace_back(std::make_unique>(this, name, def_value)); + } + + template + T get_entry_value(const std::string& name, const T& def_value) + { + if (!entries[name]) + add_entry(name, def_value); + + return convert::to(entries[name]->string_value()); + } + + template + void set_entry_value(const std::string& name, const T& value) + { + if (entries[name]) + entries[name]->string_value(convert::to(value)); + + else + add_entry(name, value); + } + + friend config_context_t; + }; + +public: + class entry_base + { + public: + virtual std::string name() = 0; + virtual void to_default() = 0; + virtual std::string string_value() = 0; + virtual void string_value(const std::string& value) = 0; + virtual void value_from(const entry_base* rhs) = 0; + }; + + template + class entry : public entry_base + { + T m_default_value; + T m_value; + group* m_parent; + std::string m_name; + + public: + entry(group* parent, const std::string& name, const T& default_value) + : m_parent(parent) + , m_name(name) + , m_default_value(default_value) + , m_value(default_value) + { + if(!parent->entries[name]) + parent->entries[name] = this; + } + + T default_value() const + { + return m_default_value; + } + + T value() const + { + return m_value; + } + + void value(const T& new_value) + { + m_value = new_value; + } + + std::string name() override + { + return m_name; + } + + void to_default() override + { + value(default_value()); + } + + std::string string_value() override + { + return convert::to(value()); + } + + void string_value(const std::string &new_value) override + { + value(convert::to(new_value)); + } + + void value_from(const entry_base* rhs) override + { + value(static_cast(rhs)->value()); + } + + entry& operator = (const T& new_value) + { + value(new_value); + return *this; + } + + template + entry& operator = (const T2& new_value) + { + value(static_cast(new_value)); + return *this; + } + + explicit operator const T&() const + { + return m_value; + } + }; + +private: + std::unordered_map m_groups; + +public: + config_context_t() = default; + + void assign(const config_context_t& rhs); + + void serialize(std::ostream& stream) const; + void deserialize(std::istream& stream); + + void set_defaults(); + + std::string to_string() const; + void from_string(const std::string&); +}; diff --git a/include/convert.h b/include/convert.h new file mode 100644 index 0000000..1e01bb4 --- /dev/null +++ b/include/convert.h @@ -0,0 +1,280 @@ +#pragma once +#include +#include "types.h" +#include "StrFmt.h" + +namespace convert +{ + template + struct to_impl_t; + + template + struct to_impl_t + { + static Type func(const Type& value) + { + return value; + } + }; + + template<> + struct to_impl_t + { + static std::string func(bool value) + { + return value ? "true" : "false"; + } + }; + + template<> + struct to_impl_t + { + static bool func(const std::string& value) + { + return value == "true" ? true : false; + } + }; + + template<> + struct to_impl_t + { + static std::string func(char value) + { + return std::to_string(value); + } + }; + + template<> + struct to_impl_t + { + static std::string func(unsigned char value) + { + return std::to_string(value); + } + }; + + + template<> + struct to_impl_t + { + static std::string func(short value) + { + return std::to_string(value); + } + }; + + template<> + struct to_impl_t + { + static std::string func(unsigned short value) + { + return std::to_string(value); + } + }; + + template<> + struct to_impl_t + { + static std::string func(int value) + { + return std::to_string(value); + } + }; + + template<> + struct to_impl_t + { + static std::string func(unsigned int value) + { + return std::to_string(value); + } + }; + + template<> + struct to_impl_t + { + static std::string func(long value) + { + return std::to_string(value); + } + }; + + template<> + struct to_impl_t + { + static std::string func(unsigned long value) + { + return std::to_string(value); + } + }; + + template<> + struct to_impl_t + { + static std::string func(long long value) + { + return std::to_string(value); + } + }; + + template<> + struct to_impl_t + { + static std::string func(unsigned long long value) + { + return std::to_string(value); + } + }; + + template<> + struct to_impl_t + { + static std::string func(float value) + { + return std::to_string(value); + } + }; + + template<> + struct to_impl_t + { + static std::string func(double value) + { + return std::to_string(value); + } + }; + + template<> + struct to_impl_t + { + static std::string func(long double value) + { + return std::to_string(value); + } + }; + + template<> + struct to_impl_t + { + static std::string func(size2i value) + { + return std::to_string(value.width) + "x" + std::to_string(value.height); + } + }; + + template<> + struct to_impl_t + { + static std::string func(position2i value) + { + return std::to_string(value.x) + ":" + std::to_string(value.y); + } + }; + + template<> + struct to_impl_t + { + static int func(const std::string& value) + { + return std::stoi(value); + } + }; + + template<> + struct to_impl_t + { + static unsigned int func(const std::string& value) + { + return (unsigned long)std::stoul(value); + } + }; + + template<> + struct to_impl_t + { + static long func(const std::string& value) + { + return std::stol(value); + } + }; + + template<> + struct to_impl_t + { + static unsigned long func(const std::string& value) + { + return std::stoul(value); + } + }; + + template<> + struct to_impl_t + { + static long long func(const std::string& value) + { + return std::stoll(value); + } + }; + + template<> + struct to_impl_t + { + static unsigned long long func(const std::string& value) + { + return std::stoull(value); + } + }; + + template<> + struct to_impl_t + { + static float func(const std::string& value) + { + return std::stof(value); + } + }; + + template<> + struct to_impl_t + { + static double func(const std::string& value) + { + return std::stod(value); + } + }; + + template<> + struct to_impl_t + { + static long double func(const std::string& value) + { + return std::stold(value); + } + }; + + template<> + struct to_impl_t + { + static size2i func(const std::string& value) + { + const auto& data = fmt::split(value, { "x" }); + return { std::stoi(data[0]), std::stoi(data[1]) }; + } + }; + + template<> + struct to_impl_t + { + static position2i func(const std::string& value) + { + const auto& data = fmt::split(value, { ":" }); + return { std::stoi(data[0]), std::stoi(data[1]) }; + } + }; + + template + ReturnType to(FromType value) + { + return to_impl_t, std::remove_all_extents_t>::func(value); + } +} diff --git a/include/event.h b/include/event.h new file mode 100644 index 0000000..1806550 --- /dev/null +++ b/include/event.h @@ -0,0 +1,409 @@ +#pragma once +#include +#include +#include + +enum class event_result +{ + skip, + handled +}; + +class events_queue +{ + std::deque> m_queue; + +public: + /*template + events_queue& operator += (event &evt) + { + evt.set_queue(this); + return *this; + } +*/ + void invoke(std::function function) + { + m_queue.push_back(function); + } + + void flush() + { + while (!m_queue.empty()) + { + std::function function = m_queue.front(); + function(); + m_queue.pop_front(); + } + } +}; + +template +class event +{ + using func_t = std::function; + using entry_t = typename std::list::iterator; + + std::list handlers; + events_queue& m_queue; + +public: + event(events_queue* queue = nullptr) : m_queue(*queue) + { + } + + void invoke(AT... args) + { + m_queue.invoke(std::bind([&](AT... eargs) + { + for (auto &&handler : handlers) + { + if (handler(eargs...) == event_result::handled) + break; + } + }, args...)); + } + + void operator()(AT... args) + { + invoke(args...); + } + + entry_t bind(func_t func) + { + handlers.push_front(func); + return handlers.begin(); + } + + template + entry_t bind(T *caller, event_result(T::*callback)(AT...)) + { + return bind([=](AT... args) { return (caller->*callback)(args...); }); + } + + template + entry_t bind(T *caller, void(T::*callback)(AT...)) + { + return bind([=](AT... args) { (caller->*callback)(args...); return event_result::skip; }); + } + + void unbind(entry_t entry) + { + handlers.erase(entry); + } + + entry_t operator +=(func_t func) + { + return bind(func); + } + + void operator -=(entry_t what) + { + return unbind(what); + } +}; + +template<> +class event +{ + using func_t = std::function; + using entry_t = std::list::iterator; + + std::list m_listeners; + events_queue* m_queue; + + void invoke_listeners() + { + for (auto &&listener : m_listeners) + { + if (listener() == event_result::handled) + break; + } + } + +public: + event(events_queue* queue = nullptr) : m_queue(queue) + { + } + + void invoke() + { + if (m_queue) + m_queue->invoke([=]() { invoke_listeners(); }); + else + invoke_listeners(); + } + + void operator()() + { + invoke(); + } + + entry_t bind(func_t func) + { + m_listeners.push_front(func); + return m_listeners.begin(); + } + + template + entry_t bind(T *caller, event_result(T::*callback)()) + { + return bind([=]() { return (caller->*callback)(); }); + } + + template + entry_t bind(T *caller, void(T::*callback)()) + { + return bind([=]() { (caller->*callback)(); return event_result::skip; }); + } + + void unbind(entry_t what) + { + m_listeners.erase(what); + } + + entry_t operator +=(func_t func) + { + return bind(func); + } + + void operator -=(entry_t what) + { + return unbind(what); + } +}; + +class event_binder_t +{ + template + class binder_impl_t + { + event_binder_t *m_binder; + event *m_event; + + public: + binder_impl_t(event_binder_t *binder, event *evt) + : m_binder(binder) + , m_event(evt) + { + } + }; + +public: + template + binder_impl_t operator()(event& evt) const + { + return{ this, &evt }; + } +}; + +template +class combined_data; + +template +class local_data +{ +public: + using type = T; + +protected: + type m_data; + + void set(type value) + { + m_data = value; + } + + type get() const + { + return m_data; + } + + bool equals(T value) const + { + return get() == value; + } + + bool invoke_event(type value) + { + return false; + } + + friend combined_data; +}; + +template +class combined_data +{ +public: + using type = T; + +protected: + local_data m_local_data; + std::function m_invoke_event_function; + std::function m_get_function; + + bool invoke_event(type value) + { + if (m_invoke_event_function) + { + m_invoke_event_function(value); + return true; + } + + return false; + } + + void set(type value) + { + m_local_data.set(value); + } + + type get() const + { + if (m_get_function) + { + return m_get_function(); + } + + return m_local_data.get(); + } + + type get_local() const + { + return m_local_data.get(); + } + + bool equals(T value) const + { + return get_local() == value; + } + +public: + void invoke_event_function(std::function function) + { + m_invoke_event_function = function; + } + + void get_function(std::function function) + { + m_get_function = function; + } +}; + +template> +class data_event : public base_type_ +{ +public: + using type = T; + using base_type = base_type_; + +protected: + event_result dochange(type new_value) + { + auto old_value = get(); + base_type::set(new_value); + onchanged(old_value); + return event_result::handled; + } + +public: + event onchange; + event onchanged; + + data_event(events_queue *queue = nullptr) + : onchange(queue) + , onchanged(queue) + { + onchange.bind(this, &data_event::dochange); + base_type::set({}); + } + + type get() const + { + return base_type::get(); + } + + type operator()() const + { + return get(); + } + + void change(type value, bool use_custom_invoke_event = true) + { + if (!base_type::equals(value)) + { + if (!use_custom_invoke_event || !base_type::invoke_event(value)) + { + onchange(value); + } + } + } + + operator const type() const + { + return get(); + } + + operator type() + { + return get(); + } + + data_event& operator = (type value) + { + change(value); + return *this; + } + + template auto operator + (aType value) const { return get() + value; } + template auto operator - (aType value) const { return get() - value; } + template auto operator * (aType value) const { return get() * value; } + template auto operator / (aType value) const { return get() / value; } + template auto operator % (aType value) const { return get() % value; } + template auto operator & (aType value) const { return get() & value; } + template auto operator | (aType value) const { return get() | value; } + template auto operator ^ (aType value) const { return get() ^ value; } + + template data_event& operator += (aType value) { return *this = get() + value; } + template data_event& operator -= (aType value) { return *this = get() - value; } + template data_event& operator *= (aType value) { return *this = get() * value; } + template data_event& operator /= (aType value) { return *this = get() / value; } + template data_event& operator %= (aType value) { return *this = get() % value; } + template data_event& operator &= (aType value) { return *this = get() & value; } + template data_event& operator |= (aType value) { return *this = get() | value; } + template data_event& operator ^= (aType value) { return *this = get() ^ value; } + + data_event& operator ++() + { + type value = get(); + return *this = ++value; + } + + type operator ++(int) + { + type value = get(); + type result = value; + *this = value++; + return result; + } + + data_event& operator --() + { + type value = get(); + return *this = --value; + } + + type operator --(int) + { + type value = get(); + type result = value; + *this = value--; + return result; + } +}; + +struct with_event_binder +{ + event_binder_t event_binder; +}; + diff --git a/include/types.h b/include/types.h new file mode 100644 index 0000000..ab011c2 --- /dev/null +++ b/include/types.h @@ -0,0 +1,1102 @@ +#pragma once + +#include +#include +#include +#include + +using uchar = unsigned char; +using ushort = unsigned short; +using uint = unsigned int; +using ulong = unsigned long; +using ullong = unsigned long long; + +using llong = long long; + +using u8 = std::uint8_t; +using u16 = std::uint16_t; +using u32 = std::uint32_t; +using u64 = std::uint64_t; + +using s8 = std::int8_t; +using s16 = std::int16_t; +using s32 = std::int32_t; +using s64 = std::int64_t; + +union alignas(2) f16 +{ + u16 _u16; + u8 _u8[2]; + + explicit f16(u16 raw) + { + _u16 = raw; + } + + explicit operator float() const + { + // See http://stackoverflow.com/a/26779139 + // The conversion doesn't handle NaN/Inf + u32 raw = ((_u16 & 0x8000) << 16) | // Sign (just moved) + (((_u16 & 0x7c00) + 0x1C000) << 13) | // Exponent ( exp - 15 + 127) + ((_u16 & 0x03FF) << 13); // Mantissa + return (float&)raw; + } +}; + +using f32 = float; +using f64 = double; + +struct ignore +{ + template + ignore(T) + { + } +}; + +template +struct size2_base +{ + T width, height; + + constexpr size2_base() : width{}, height{} + { + } + + constexpr size2_base(T width, T height) : width{ width }, height{ height } + { + } + + constexpr size2_base(const size2_base& rhs) : width{ rhs.width }, height{ rhs.height } + { + } + + constexpr size2_base operator -(const size2_base& rhs) const + { + return{ width - rhs.width, height - rhs.height }; + } + constexpr size2_base operator -(T rhs) const + { + return{ width - rhs, height - rhs }; + } + constexpr size2_base operator +(const size2_base& rhs) const + { + return{ width + rhs.width, height + rhs.height }; + } + constexpr size2_base operator +(T rhs) const + { + return{ width + rhs, height + rhs }; + } + constexpr size2_base operator /(const size2_base& rhs) const + { + return{ width / rhs.width, height / rhs.height }; + } + constexpr size2_base operator /(T rhs) const + { + return{ width / rhs, height / rhs }; + } + constexpr size2_base operator *(const size2_base& rhs) const + { + return{ width * rhs.width, height * rhs.height }; + } + constexpr size2_base operator *(T rhs) const + { + return{ width * rhs, height * rhs }; + } + + size2_base& operator -=(const size2_base& rhs) + { + width -= rhs.width; + height -= rhs.height; + return *this; + } + size2_base& operator -=(T rhs) + { + width -= rhs; + height -= rhs; + return *this; + } + size2_base& operator +=(const size2_base& rhs) + { + width += rhs.width; + height += rhs.height; + return *this; + } + size2_base& operator +=(T rhs) + { + width += rhs; + height += rhs; + return *this; + } + size2_base& operator /=(const size2_base& rhs) + { + width /= rhs.width; + height /= rhs.height; + return *this; + } + size2_base& operator /=(T rhs) + { + width /= rhs; + height /= rhs; + return *this; + } + size2_base& operator *=(const size2_base& rhs) + { + width *= rhs.width; + height *= rhs.height; + return *this; + } + size2_base& operator *=(T rhs) + { + width *= rhs; + height *= rhs; + return *this; + } + + constexpr bool operator == (const size2_base& rhs) const + { + return width == rhs.width && height == rhs.height; + } + + constexpr bool operator != (const size2_base& rhs) const + { + return width != rhs.width || height != rhs.height; + } + + template + constexpr operator size2_base() const + { + return{ (NT)width, (NT)height }; + } +}; + +template +struct position1_base +{ + T x; + + position1_base operator -(const position1_base& rhs) const + { + return{ x - rhs.x }; + } + position1_base operator -(T rhs) const + { + return{ x - rhs }; + } + position1_base operator +(const position1_base& rhs) const + { + return{ x + rhs.x }; + } + position1_base operator +(T rhs) const + { + return{ x + rhs }; + } + template + position1_base operator *(RhsT rhs) const + { + return{ T(x * rhs) }; + } + position1_base operator *(const position1_base& rhs) const + { + return{ T(x * rhs.x) }; + } + template + position1_base operator /(RhsT rhs) const + { + return{ x / rhs }; + } + position1_base operator /(const position1_base& rhs) const + { + return{ x / rhs.x }; + } + + position1_base& operator -=(const position1_base& rhs) + { + x -= rhs.x; + return *this; + } + position1_base& operator -=(T rhs) + { + x -= rhs; + return *this; + } + position1_base& operator +=(const position1_base& rhs) + { + x += rhs.x; + return *this; + } + position1_base& operator +=(T rhs) + { + x += rhs; + return *this; + } + + template + position1_base& operator *=(RhsT rhs) const + { + x *= rhs; + return *this; + } + position1_base& operator *=(const position1_base& rhs) const + { + x *= rhs.x; + return *this; + } + template + position1_base& operator /=(RhsT rhs) const + { + x /= rhs; + return *this; + } + position1_base& operator /=(const position1_base& rhs) const + { + x /= rhs.x; + return *this; + } + + bool operator ==(const position1_base& rhs) const + { + return x == rhs.x; + } + + bool operator ==(T rhs) const + { + return x == rhs; + } + + bool operator !=(const position1_base& rhs) const + { + return !(*this == rhs); + } + + bool operator !=(T rhs) const + { + return !(*this == rhs); + } + + template + operator position1_base() const + { + return{ (NT)x }; + } + + double distance(const position1_base& to) + { + return abs(x - to.x); + } +}; + +template +struct position2_base +{ + T x, y; + + constexpr position2_base() : x{}, y{} + { + } + + constexpr position2_base(T x, T y) : x{ x }, y{ y } + { + } + + constexpr position2_base(const position2_base& rhs) : x{ rhs.x }, y{ rhs.y } + { + } + + constexpr bool operator >(const position2_base& rhs) const + { + return x > rhs.x && y > rhs.y; + } + constexpr bool operator >(T rhs) const + { + return x > rhs && y > rhs; + } + constexpr bool operator <(const position2_base& rhs) const + { + return x < rhs.x && y < rhs.y; + } + constexpr bool operator <(T rhs) const + { + return x < rhs && y < rhs; + } + constexpr bool operator >=(const position2_base& rhs) const + { + return x >= rhs.x && y >= rhs.y; + } + constexpr bool operator >=(T rhs) const + { + return x >= rhs && y >= rhs; + } + constexpr bool operator <=(const position2_base& rhs) const + { + return x <= rhs.x && y <= rhs.y; + } + constexpr bool operator <=(T rhs) const + { + return x <= rhs && y <= rhs; + } + + constexpr position2_base operator -(const position2_base& rhs) const + { + return{ x - rhs.x, y - rhs.y }; + } + constexpr position2_base operator -(T rhs) const + { + return{ x - rhs, y - rhs }; + } + constexpr position2_base operator +(const position2_base& rhs) const + { + return{ x + rhs.x, y + rhs.y }; + } + constexpr position2_base operator +(T rhs) const + { + return{ x + rhs, y + rhs }; + } + template + constexpr position2_base operator *(RhsT rhs) const + { + return{ T(x * rhs), T(y * rhs) }; + } + constexpr position2_base operator *(const position2_base& rhs) const + { + return{ T(x * rhs.x), T(y * rhs.y) }; + } + template + constexpr position2_base operator /(RhsT rhs) const + { + return{ x / rhs, y / rhs }; + } + constexpr position2_base operator /(const position2_base& rhs) const + { + return{ x / rhs.x, y / rhs.y }; + } + constexpr position2_base operator /(const size2_base& rhs) const + { + return{ x / rhs.width, y / rhs.height }; + } + + position2_base& operator -=(const position2_base& rhs) + { + x -= rhs.x; + y -= rhs.y; + return *this; + } + position2_base& operator -=(T rhs) + { + x -= rhs; + y -= rhs; + return *this; + } + position2_base& operator +=(const position2_base& rhs) + { + x += rhs.x; + y += rhs.y; + return *this; + } + position2_base& operator +=(T rhs) + { + x += rhs; + y += rhs; + return *this; + } + + template + position2_base& operator *=(RhsT rhs) + { + x *= rhs; + y *= rhs; + return *this; + } + position2_base& operator *=(const position2_base& rhs) + { + x *= rhs.x; + y *= rhs.y; + return *this; + } + template + position2_base& operator /=(RhsT rhs) + { + x /= rhs; + y /= rhs; + return *this; + } + position2_base& operator /=(const position2_base& rhs) + { + x /= rhs.x; + y /= rhs.y; + return *this; + } + + constexpr bool operator ==(const position2_base& rhs) const + { + return x == rhs.x && y == rhs.y; + } + + constexpr bool operator ==(T rhs) const + { + return x == rhs && y == rhs; + } + + constexpr bool operator !=(const position2_base& rhs) const + { + return !(*this == rhs); + } + + constexpr bool operator !=(T rhs) const + { + return !(*this == rhs); + } + + template + constexpr operator position2_base() const + { + return{ (NT)x, (NT)y }; + } + + double distance(const position2_base& to) const + { + return std::sqrt(double((x - to.x) * (x - to.x) + (y - to.y) * (y - to.y))); + } +}; + +template +struct position3_base +{ + T x, y, z; + /* + position3_base() : x{}, y{}, z{} + { + } + + position3_base(T x, T y, T z) : x{ x }, y{ y }, z{ z } + { + } + */ + + position3_base operator -(const position3_base& rhs) const + { + return{ x - rhs.x, y - rhs.y, z - rhs.z }; + } + position3_base operator -(T rhs) const + { + return{ x - rhs, y - rhs, z - rhs }; + } + position3_base operator +(const position3_base& rhs) const + { + return{ x + rhs.x, y + rhs.y, z + rhs.z }; + } + position3_base operator +(T rhs) const + { + return{ x + rhs, y + rhs, z + rhs }; + } + + position3_base& operator -=(const position3_base& rhs) + { + x -= rhs.x; + y -= rhs.y; + z -= rhs.z; + return *this; + } + position3_base& operator -=(T rhs) + { + x -= rhs; + y -= rhs; + z -= rhs; + return *this; + } + position3_base& operator +=(const position3_base& rhs) + { + x += rhs.x; + y += rhs.y; + z += rhs.z; + return *this; + } + position3_base& operator +=(T rhs) + { + x += rhs; + y += rhs; + z += rhs; + return *this; + } + + bool operator ==(const position3_base& rhs) const + { + return x == rhs.x && y == rhs.y && z == rhs.z; + } + + bool operator ==(T rhs) const + { + return x == rhs && y == rhs && z == rhs; + } + + bool operator !=(const position3_base& rhs) const + { + return !(*this == rhs); + } + + bool operator !=(T rhs) const + { + return !(*this == rhs); + } + + template + operator position3_base() const + { + return{ (NT)x, (NT)y, (NT)z }; + } +}; + +template +struct position4_base +{ + T x, y, z, w; + + constexpr position4_base() : x{}, y{}, z{}, w{} + { + } + + constexpr position4_base(T x, T y = {}, T z = {}, T w = {T(1)}) : x{ x }, y{ y }, z{ z }, w{ w } + { + } + + constexpr position4_base operator -(const position4_base& rhs) const + { + return{ x - rhs.x, y - rhs.y, z - rhs.z, w - rhs.w }; + } + constexpr position4_base operator -(T rhs) const + { + return{ x - rhs, y - rhs, z - rhs, w - rhs }; + } + constexpr position4_base operator +(const position4_base& rhs) const + { + return{ x + rhs.x, y + rhs.y, z + rhs.z, w + rhs.w }; + } + constexpr position4_base operator +(T rhs) const + { + return{ x + rhs, y + rhs, z + rhs, w + rhs }; + } + + position4_base& operator -=(const position4_base& rhs) + { + x -= rhs.x; + y -= rhs.y; + z -= rhs.z; + w -= rhs.w; + return *this; + } + position4_base& operator -=(T rhs) + { + x -= rhs; + y -= rhs; + z -= rhs; + w -= rhs; + return *this; + } + position4_base& operator +=(const position4_base& rhs) + { + x += rhs.x; + y += rhs.y; + z += rhs.z; + w += rhs.w; + return *this; + } + position4_base& operator +=(T rhs) + { + x += rhs; + y += rhs; + z += rhs; + w += rhs; + return *this; + } + + constexpr bool operator ==(const position4_base& rhs) const + { + return x == rhs.x && y == rhs.y && z == rhs.z && w == rhs.w; + } + + constexpr bool operator ==(T rhs) const + { + return x == rhs && y == rhs && z == rhs && w == rhs; + } + + constexpr bool operator !=(const position4_base& rhs) const + { + return !(*this == rhs); + } + + constexpr bool operator !=(T rhs) const + { + return !(*this == rhs); + } + + template + constexpr operator position4_base() const + { + return{ (NT)x, (NT)y, (NT)z, (NT)w }; + } +}; + +template +using position_base = position2_base; + +template +struct coord_base +{ + union + { + position_base position; + struct { T x, y; }; + }; + + union + { + size2_base size; + struct { T width, height; }; + }; + + constexpr coord_base() : position{}, size{} +#ifdef _MSC_VER + //compiler error + , x{}, y{}, width{}, height{} +#endif + { + } + + constexpr coord_base(const position_base& position, const size2_base& size) + : position{ position }, size{ size } +#ifdef _MSC_VER + , x{ position.x }, y{ position.y }, width{ size.width }, height{ size.height } +#endif + { + } + + constexpr coord_base(T x, T y, T width, T height) : x{ x }, y{ y }, width{ width }, height{ height } + { + } + + constexpr bool test(const position_base& position) const + { + if (position.x < x || position.x >= x + width) + return false; + + if (position.y < y || position.y >= y + height) + return false; + + return true; + } + + constexpr bool operator == (const coord_base& rhs) const + { + return position == rhs.position && size == rhs.size; + } + + constexpr bool operator != (const coord_base& rhs) const + { + return position != rhs.position || size != rhs.size; + } + + template + constexpr operator coord_base() const + { + return{ (NT)x, (NT)y, (NT)width, (NT)height }; + } +}; + +template +struct area_base +{ + T x1, x2; + T y1, y2; + + constexpr area_base() : x1{}, x2{}, y1{}, y2{} + { + } + + constexpr area_base(T x1, T y1, T x2, T y2) : x1{ x1 }, x2{ x2 }, y1{ y1 }, y2{ y2 } + { + } + + constexpr area_base(const coord_base& coord) : x1{ coord.x }, x2{ coord.x + coord.width }, y1{ coord.y }, y2{ coord.y + coord.height } + { + } + + constexpr operator coord_base() const + { + return{ x1, y1, x2 - x1, y2 - y1 }; + } + + void flip_vertical() + { + std::swap(y1, y2); + } + + void flip_horizontal() + { + std::swap(x1, x2); + } + + constexpr area_base flipped_vertical() const + { + return{ x1, y2, x2, y1 }; + } + + constexpr area_base flipped_horizontal() const + { + return{ x2, y1, x1, y2 }; + } + + constexpr bool operator == (const area_base& rhs) const + { + return x1 == rhs.x1 && x2 == rhs.x2 && y1 == rhs.y1 && y2 == rhs.y2; + } + + constexpr bool operator != (const area_base& rhs) const + { + return !(*this == rhs); + } + + constexpr area_base operator - (const size2_base& size) const + { + return{ x1 - size.width, y1 - size.height, x2 - size.width, y2 - size.height }; + } + constexpr area_base operator - (const T& value) const + { + return{ x1 - value, y1 - value, x2 - value, y2 - value }; + } + constexpr area_base operator + (const size2_base& size) const + { + return{ x1 + size.width, y1 + size.height, x2 + size.width, y2 + size.height }; + } + constexpr area_base operator + (const T& value) const + { + return{ x1 + value, y1 + value, x2 + value, y2 + value }; + } + constexpr area_base operator / (const size2_base& size) const + { + return{ x1 / size.width, y1 / size.height, x2 / size.width, y2 / size.height }; + } + constexpr area_base operator / (const T& value) const + { + return{ x1 / value, y1 / value, x2 / value, y2 / value }; + } + constexpr area_base operator * (const size2_base& size) const + { + return{ x1 * size.width, y1 * size.height, x2 * size.width, y2 * size.height }; + } + constexpr area_base operator * (const T& value) const + { + return{ x1 * value, y1 * value, x2 * value, y2 * value }; + } + + template + constexpr operator area_base() const + { + return{(NT)x1, (NT)y1, (NT)x2, (NT)y2}; + } +}; + +template +struct size3_base +{ + T width, height, depth; + /* + size3_base() : width{}, height{}, depth{} + { + } + + size3_base(T width, T height, T depth) : width{ width }, height{ height }, depth{ depth } + { + } + */ +}; + +template +struct coord3_base +{ + union + { + position3_base position; + struct { T x, y, z; }; + }; + + union + { + size3_base size; + struct { T width, height, depth; }; + }; + + constexpr coord3_base() : position{}, size{} + { + } + + constexpr coord3_base(const position3_base& position, const size3_base& size) : position{ position }, size{ size } + { + } + + constexpr coord3_base(T x, T y, T z, T width, T height, T depth) : x{ x }, y{ y }, z{ z }, width{ width }, height{ height }, depth{ depth } + { + } + + constexpr bool test(const position3_base& position) const + { + if (position.x < x || position.x >= x + width) + return false; + + if (position.y < y || position.y >= y + height) + return false; + + if (position.z < z || position.z >= z + depth) + return false; + + return true; + } + + template + constexpr operator coord3_base() const + { + return{ (NT)x, (NT)y, (NT)z, (NT)width, (NT)height, (NT)depth }; + } +}; + + +template +struct color4_base +{ + union + { + struct + { + T r, g, b, a; + }; + + struct + { + T x, y, z, w; + }; + + T rgba[4]; + T xyzw[4]; + }; + + color4_base() + : x{} + , y{} + , z{} + , w{ T(1) } + { + } + + color4_base(T x, T y = {}, T z = {}, T w = {}) + : x(x) + , y(y) + , z(z) + , w(w) + { + } + + bool operator == (const color4_base& rhs) const + { + return r == rhs.r && g == rhs.g && b == rhs.b && a == rhs.a; + } + + bool operator != (const color4_base& rhs) const + { + return !(*this == rhs); + } + + template + operator color4_base() const + { + return{ (NT)x, (NT)y, (NT)z, (NT)w }; + } +}; + +template +struct color3_base +{ + union + { + struct + { + T r, g, b; + }; + + struct + { + T x, y, z; + }; + + T rgb[3]; + T xyz[3]; + }; + + constexpr color3_base(T x = {}, T y = {}, T z = {}) + : x(x) + , y(y) + , z(z) + { + } + + constexpr bool operator == (const color3_base& rhs) const + { + return r == rhs.r && g == rhs.g && b == rhs.b; + } + + constexpr bool operator != (const color3_base& rhs) const + { + return !(*this == rhs); + } + + template + constexpr operator color3_base() const + { + return{ (NT)x, (NT)y, (NT)z }; + } +}; + +template +struct color2_base +{ + union + { + struct + { + T r, g; + }; + + struct + { + T x, y; + }; + + T rg[2]; + T xy[2]; + }; + + constexpr color2_base(T x = {}, T y = {}) + : x(x) + , y(y) + { + } + + constexpr bool operator == (const color2_base& rhs) const + { + return r == rhs.r && g == rhs.g; + } + + constexpr bool operator != (const color2_base& rhs) const + { + return !(*this == rhs); + } + + template + constexpr operator color2_base() const + { + return{ (NT)x, (NT)y }; + } +}; + +template +struct color1_base +{ + union + { + T r; + T x; + }; + + constexpr color1_base(T x = {}) + : x(x) + { + } + + constexpr bool operator == (const color1_base& rhs) const + { + return r == rhs.r; + } + + constexpr bool operator != (const color1_base& rhs) const + { + return !(*this == rhs); + } + + template + constexpr operator color1_base() const + { + return{ (NT)x }; + } +}; + +//specializations +using positioni = position_base; +using positionf = position_base; +using positiond = position_base; + +using coordi = coord_base; +using coordf = coord_base; +using coordd = coord_base; + +using areai = area_base; +using areaf = area_base; +using aread = area_base; + +using position1i = position1_base; +using position1f = position1_base; +using position1d = position1_base; + +using position2i = position2_base; +using position2f = position2_base; +using position2d = position2_base; + +using position3i = position3_base; +using position3f = position3_base; +using position3d = position3_base; + +using position4i = position4_base; +using position4f = position4_base; +using position4d = position4_base; + +using size2i = size2_base; +using size2f = size2_base; +using size2d = size2_base; + +using sizei = size2i; +using sizef = size2f; +using sized = size2d; + +using size3i = size3_base; +using size3f = size3_base; +using size3d = size3_base; + +using coord3i = coord3_base; +using coord3f = coord3_base; +using coord3d = coord3_base; + +using color4i = color4_base; +using color4f = color4_base; +using color4d = color4_base; + +using color3i = color3_base; +using color3f = color3_base; +using color3d = color3_base; + +using color2i = color2_base; +using color2f = color2_base; +using color2d = color2_base; + +using color1i = color1_base; +using color1f = color1_base; +using color1d = color1_base; + +namespace std +{ + template<> + struct hash<::position2i> + { + size_t operator()(const ::position2i& position) const + { + return (static_cast(position.x) << 32) | position.y; + } + }; +} diff --git a/src/File.cpp b/src/File.cpp new file mode 100644 index 0000000..c5a11fd --- /dev/null +++ b/src/File.cpp @@ -0,0 +1,881 @@ +#include "Log.h" +#include "File.h" + +#ifdef _WIN32 +#define _WIN32_WINNT 0x0601 +#include + +#define GET_API_ERROR static_cast(GetLastError()) + +static std::unique_ptr to_wchar(const std::string& source) +{ + const auto length = source.size() + 1; // size + null terminator + + const int size = source.size() < INT_MAX ? static_cast(length) : throw EXCEPTION("Invalid source length (0x%llx)", source.size()); + + std::unique_ptr buffer(new wchar_t[length]); // allocate buffer assuming that length is the max possible size + + if (!MultiByteToWideChar(CP_UTF8, 0, source.c_str(), size, buffer.get(), size)) + { + throw EXCEPTION("System error 0x%x", GetLastError()); + } + + return buffer; +} + +static void to_utf8(std::string& result, const wchar_t* source) +{ + const int length = lstrlenW(source); // source length + + if (length == 0) + { + return result.clear(); + } + + const int size = WideCharToMultiByte(CP_UTF8, 0, source, length, NULL, 0, NULL, NULL); // output size + + if (size <= 0) + { + throw EXCEPTION("System error 0x%x", GetLastError()); + } + + result.resize(size); + + if (!WideCharToMultiByte(CP_UTF8, 0, source, length, &result.front(), size, NULL, NULL)) + { + throw EXCEPTION("System error 0x%x", GetLastError()); + } +} + +static time_t to_time(const ULARGE_INTEGER& ft) +{ + return ft.QuadPart / 10000000ULL - 11644473600ULL; +} + +static time_t to_time(const LARGE_INTEGER& ft) +{ + ULARGE_INTEGER v; + v.LowPart = ft.LowPart; + v.HighPart = ft.HighPart; + + return to_time(v); +} + +static time_t to_time(const FILETIME& ft) +{ + ULARGE_INTEGER v; + v.LowPart = ft.dwLowDateTime; + v.HighPart = ft.dwHighDateTime; + + return to_time(v); +} + +static bool truncate_file(const std::string& file, u64 length) +{ + // open the file + const auto handle = CreateFileW(to_wchar(file).get(), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (handle == INVALID_HANDLE_VALUE) + { + return false; + } + + LARGE_INTEGER distance; + distance.QuadPart = length; + + // seek and truncate + if (!SetFilePointerEx(handle, distance, NULL, FILE_BEGIN) || !SetEndOfFile(handle)) + { + const auto error = GetLastError(); + CloseHandle(handle); + SetLastError(error); + return false; + } + + return CloseHandle(handle); +} + +#else +#include +#include +#include +#include +#include +#include +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#include +#else +#include +#endif +#include + +#define GET_API_ERROR static_cast(errno) + +#endif + +thread_local fse fs::g_tls_error = fse::ok; + +bool fs::stat(const std::string& path, stat_t& info) +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + WIN32_FILE_ATTRIBUTE_DATA attrs; + if (!GetFileAttributesExW(to_wchar(path).get(), GetFileExInfoStandard, &attrs)) + { + return false; + } + + info.is_directory = (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + info.is_writable = (attrs.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0; + info.size = (u64)attrs.nFileSizeLow | ((u64)attrs.nFileSizeHigh << 32); + info.atime = to_time(attrs.ftLastAccessTime); + info.mtime = to_time(attrs.ftLastWriteTime); + info.ctime = to_time(attrs.ftCreationTime); +#else + struct stat file_info; + if (stat(path.c_str(), &file_info) < 0) + { + return false; + } + + info.is_directory = S_ISDIR(file_info.st_mode); + info.is_writable = file_info.st_mode & 0200; // HACK: approximation + info.size = file_info.st_size; + info.atime = file_info.st_atime; + info.mtime = file_info.st_mtime; + info.ctime = file_info.st_ctime; +#endif + + return true; +} + +bool fs::exists(const std::string& path) +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + return GetFileAttributesW(to_wchar(path).get()) != 0xFFFFFFFF; +#else + struct stat buffer; + return stat(path.c_str(), &buffer) == 0; +#endif +} + +bool fs::is_file(const std::string& file) +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + DWORD attrs; + if ((attrs = GetFileAttributesW(to_wchar(file).get())) == INVALID_FILE_ATTRIBUTES) + { + return false; + } + + return (attrs & FILE_ATTRIBUTE_DIRECTORY) == 0; +#else + struct stat file_info; + if (stat(file.c_str(), &file_info) < 0) + { + return false; + } + + return !S_ISDIR(file_info.st_mode); +#endif +} + +bool fs::is_dir(const std::string& dir) +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + DWORD attrs; + if ((attrs = GetFileAttributesW(to_wchar(dir).get())) == INVALID_FILE_ATTRIBUTES) + { + return false; + } + + return (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0; +#else + struct stat file_info; + if (stat(dir.c_str(), &file_info) < 0) + { + return false; + } + + return S_ISDIR(file_info.st_mode); +#endif +} + +bool fs::create_dir(const std::string& dir) +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + if (!CreateDirectoryW(to_wchar(dir).get(), NULL)) +#else + if (mkdir(dir.c_str(), 0777)) +#endif + { + LOG_WARNING(GENERAL, "Error creating directory '%s': 0x%llx", dir, GET_API_ERROR); + return false; + } + + return true; +} + +bool fs::create_path(const std::string& path) +{ + g_tls_error = fse::ok; + + size_t start = 0; + + while (true) + { + // maybe it could be more optimal if goes from the end recursively + size_t pos = path.find_first_of("/\\", start); + + if (pos == std::string::npos) + { + pos = path.length(); + } + + std::string dir = path.substr(0, pos); + + start = ++pos; + + if (dir.size() == 0) + { + continue; + } + + if (!is_dir(dir)) + { + // if doesn't exist or not a dir + if (!create_dir(dir)) + { + // if creating failed + return false; + } + } + + if (pos >= path.length()) + { + break; + } + } + + return true; +} + +bool fs::remove_dir(const std::string& dir) +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + if (!RemoveDirectoryW(to_wchar(dir).get())) +#else + if (rmdir(dir.c_str())) +#endif + { + LOG_WARNING(GENERAL, "Error deleting directory '%s': 0x%llx", dir, GET_API_ERROR); + return false; + } + + return true; +} + +bool fs::rename(const std::string& from, const std::string& to) +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + if (!MoveFileW(to_wchar(from).get(), to_wchar(to).get())) +#else + if (rename(from.c_str(), to.c_str())) +#endif + { + LOG_WARNING(GENERAL, "Error renaming '%s' to '%s': 0x%llx", from, to, GET_API_ERROR); + return false; + } + + return true; +} + +#ifndef _WIN32 + +static int OSCopyFile(const char* source, const char* destination, bool overwrite) +{ + /* Source: http://stackoverflow.com/questions/2180079/how-can-i-copy-a-file-on-unix-using-c */ + + const int input = open(source, O_RDONLY); + if (input == -1) + { + return -1; + } + + const int output = open(destination, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : O_EXCL), 0666); + if (output == -1) + { + close(input); + return -1; + } + + //Here we use kernel-space copying for performance reasons +#if defined(__APPLE__) || defined(__FreeBSD__) + //fcopyfile works on FreeBSD and OS X 10.5+ + const int result = fcopyfile(input, output, 0, COPYFILE_ALL); +#else + //sendfile will work with non-socket output (i.e. regular file) on Linux 2.6.33+ + off_t bytesCopied = 0; + struct stat fileinfo = { 0 }; + const int result = fstat(input, &fileinfo) == -1 || sendfile(output, input, &bytesCopied, fileinfo.st_size) == -1 ? -1 : 0; +#endif + + close(input); + close(output); + + return result; +} +#endif + +bool fs::copy_file(const std::string& from, const std::string& to, bool overwrite) +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + if (!CopyFileW(to_wchar(from).get(), to_wchar(to).get(), !overwrite)) +#else + if (OSCopyFile(from.c_str(), to.c_str(), overwrite)) +#endif + { + LOG_WARNING(GENERAL, "Error copying '%s' to '%s': 0x%llx", from, to, GET_API_ERROR); + return false; + } + + return true; +} + +bool fs::remove_file(const std::string& file) +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + if (!DeleteFileW(to_wchar(file).get())) +#else + if (unlink(file.c_str())) +#endif + { + LOG_WARNING(GENERAL, "Error deleting file '%s': 0x%llx", file, GET_API_ERROR); + return false; + } + + return true; +} + +bool fs::truncate_file(const std::string& file, u64 length) +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + if (!::truncate_file(file, length)) +#else + if (::truncate(file.c_str(), length)) +#endif + { + LOG_WARNING(GENERAL, "Error resizing file '%s' to 0x%llx: 0x%llx", file, length, GET_API_ERROR); + return false; + } + + return true; +} + +fs::file::~file() +{ + if (m_fd != null) + { +#ifdef _WIN32 + CloseHandle((HANDLE)m_fd); +#else + ::close(m_fd); +#endif + } +} + +bool fs::file::open(const std::string& filename, u32 mode) +{ + this->close(); + + g_tls_error = fse::ok; + +#ifdef _WIN32 + DWORD access = 0; + switch (mode & (fom::read | fom::write | fom::append)) + { + case fom::read: access |= GENERIC_READ; break; + case fom::read | fom::append: access |= GENERIC_READ; break; + case fom::write: access |= GENERIC_WRITE; break; + case fom::write | fom::append: access |= FILE_APPEND_DATA; break; + case fom::read | fom::write: access |= GENERIC_READ | GENERIC_WRITE; break; + case fom::read | fom::write | fom::append: access |= GENERIC_READ | FILE_APPEND_DATA; break; + default: + { + LOG_ERROR(GENERAL, "fs::file::open('%s') failed: neither fom::read nor fom::write specified (0x%x)", filename, mode); + return false; + } + } + + DWORD disp = 0; + switch (mode & (fom::create | fom::trunc | fom::excl)) + { + case 0: disp = OPEN_EXISTING; break; + case fom::create: disp = OPEN_ALWAYS; break; + case fom::trunc: disp = TRUNCATE_EXISTING; break; + case fom::create | fom::trunc: disp = CREATE_ALWAYS; break; + case fom::create | fom::excl: disp = CREATE_NEW; break; + case fom::create | fom::excl | fom::trunc: disp = CREATE_NEW; break; + } + + if (!disp || (mode & ~(fom::read | fom::write | fom::append | fom::create | fom::trunc | fom::excl))) + { + LOG_ERROR(GENERAL, "fs::file::open('%s') failed: unknown mode specified (0x%x)", filename, mode); + return false; + } + + m_fd = (std::intptr_t)CreateFileW(to_wchar(filename).get(), access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, disp, FILE_ATTRIBUTE_NORMAL, NULL); +#else + int flags = 0; + + switch (mode & (fom::read | fom::write)) + { + case fom::read: flags |= O_RDONLY; break; + case fom::write: flags |= O_WRONLY; break; + case fom::read | fom::write: flags |= O_RDWR; break; + default: + { + LOG_ERROR(GENERAL, "fs::file::open('%s') failed: neither fom::read nor fom::write specified (0x%x)", filename, mode); + return false; + } + } + + if (mode & fom::append) flags |= O_APPEND; + if (mode & fom::create) flags |= O_CREAT; + if (mode & fom::trunc) flags |= O_TRUNC; + if (mode & fom::excl) flags |= O_EXCL; + + if (((mode & fom::excl) && !(mode & fom::create)) || (mode & ~(fom::read | fom::write | fom::append | fom::create | fom::trunc | fom::excl))) + { + LOG_ERROR(GENERAL, "fs::file::open('%s') failed: unknown mode specified (0x%x)", filename, mode); + return false; + } + + m_fd = ::open(filename.c_str(), flags, 0666); +#endif + + if (m_fd == null) + { + LOG_WARNING(GENERAL, "fs::file::open('%s', 0x%x) failed: error 0x%llx", filename, mode, GET_API_ERROR); + return false; + } + + return true; +} + +bool fs::file::trunc(u64 size) const +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + LARGE_INTEGER old, pos; + + pos.QuadPart = 0; + SetFilePointerEx((HANDLE)m_fd, pos, &old, FILE_CURRENT); // get old position + + pos.QuadPart = size; + SetFilePointerEx((HANDLE)m_fd, pos, NULL, FILE_BEGIN); // set new position + + SetEndOfFile((HANDLE)m_fd); // change file size + + SetFilePointerEx((HANDLE)m_fd, old, NULL, FILE_BEGIN); // restore position + + return true; // TODO +#else + return !::ftruncate(m_fd, size); +#endif +} + +bool fs::file::stat(stat_t& info) const +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + FILE_BASIC_INFO basic_info; + + if (!GetFileInformationByHandleEx((HANDLE)m_fd, FileBasicInfo, &basic_info, sizeof(FILE_BASIC_INFO))) + { + return false; + } + + info.is_directory = (basic_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + info.is_writable = (basic_info.FileAttributes & FILE_ATTRIBUTE_READONLY) == 0; + info.size = this->size(); + info.atime = to_time(basic_info.LastAccessTime); + info.mtime = to_time(basic_info.ChangeTime); + info.ctime = to_time(basic_info.CreationTime); +#else + struct stat file_info; + if (fstat(m_fd, &file_info) < 0) + { + return false; + } + + info.is_directory = S_ISDIR(file_info.st_mode); + info.is_writable = file_info.st_mode & 0200; // HACK: approximation + info.size = file_info.st_size; + info.atime = file_info.st_atime; + info.mtime = file_info.st_mtime; + info.ctime = file_info.st_ctime; +#endif + + return true; +} + +bool fs::file::close() +{ + g_tls_error = fse::ok; + + if (m_fd == null) + { + return false; + } + + auto fd = m_fd; + m_fd = null; + +#ifdef _WIN32 + return CloseHandle((HANDLE)fd); +#else + return !::close(fd); +#endif +} + +u64 fs::file::read(void* buffer, u64 count) const +{ + g_tls_error = fse::ok; + + // TODO (call ReadFile multiple times if count is too big) + const int size = count <= INT_MAX ? static_cast(count) : throw EXCEPTION("Invalid count (0x%llx)", count); + +#ifdef _WIN32 + DWORD nread; + if (!ReadFile((HANDLE)m_fd, buffer, size, &nread, NULL)) + { + return -1; + } + + return nread; +#else + return ::read(m_fd, buffer, size); +#endif +} + +u64 fs::file::write(const void* buffer, u64 count) const +{ + g_tls_error = fse::ok; + + // TODO (call WriteFile multiple times if count is too big) + const int size = count <= INT_MAX ? static_cast(count) : throw EXCEPTION("Invalid count (0x%llx)", count); + +#ifdef _WIN32 + DWORD nwritten; + if (!WriteFile((HANDLE)m_fd, buffer, size, &nwritten, NULL)) + { + return -1; + } + + return nwritten; +#else + return ::write(m_fd, buffer, size); +#endif +} + +u64 fs::file::seek(s64 offset, fsm seek_mode) const +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + LARGE_INTEGER pos; + pos.QuadPart = offset; + + const DWORD mode = + seek_mode == fsm::begin ? FILE_BEGIN : + seek_mode == fsm::cur ? FILE_CURRENT : + seek_mode == fsm::end ? FILE_END : + throw EXCEPTION("Unknown seek_mode (0x%x)", seek_mode); + + if (!SetFilePointerEx((HANDLE)m_fd, pos, &pos, mode)) + { + return -1; + } + + return pos.QuadPart; +#else + const int whence = + seek_mode == fsm::begin ? SEEK_SET : + seek_mode == fsm::cur ? SEEK_CUR : + seek_mode == fsm::end ? SEEK_END : + throw EXCEPTION("Unknown seek_mode (0x%x)", seek_mode); + + return ::lseek(m_fd, offset, whence); +#endif +} + +u64 fs::file::size() const +{ + g_tls_error = fse::ok; + +#ifdef _WIN32 + LARGE_INTEGER size; + if (!GetFileSizeEx((HANDLE)m_fd, &size)) + { + return -1; + } + + return size.QuadPart; +#else + struct stat file_info; + if (::fstat(m_fd, &file_info) < 0) + { + return -1; + } + + return file_info.st_size; +#endif +} + +fs::dir::~dir() +{ + if (m_path) + { +#ifdef _WIN32 + if (m_dd != -1) FindClose((HANDLE)m_dd); +#else + ::closedir((DIR*)m_dd); +#endif + } +} + +void fs::file_ptr::reset(const file& f) +{ + reset(); + + if (f) + { +#ifdef _WIN32 + const HANDLE handle = ::CreateFileMapping((HANDLE)f.m_fd, NULL, PAGE_READONLY, 0, 0, NULL); + m_ptr = (char*)::MapViewOfFile(handle, FILE_MAP_READ, 0, 0, 0); + m_size = f.size(); + ::CloseHandle(handle); +#else + m_ptr = (char*)::mmap(nullptr, m_size = f.size(), PROT_READ, MAP_SHARED, f.m_fd, 0); + if (m_ptr == (void*)-1) m_ptr = nullptr; +#endif + } +} + +void fs::file_ptr::reset() +{ + if (m_ptr) + { +#ifdef _WIN32 + ::UnmapViewOfFile(m_ptr); +#else + ::munmap(m_ptr, m_size); +#endif + } +} + +bool fs::dir::open(const std::string& dirname) +{ + this->close(); + + g_tls_error = fse::ok; + + if (!is_dir(dirname)) + { + return false; + } + + m_path.reset(new char[dirname.size() + 1]); + std::memcpy(m_path.get(), dirname.c_str(), dirname.size() + 1); + +#ifdef _WIN32 + m_dd = -1; +#else + m_dd = (std::intptr_t)::opendir(m_path.get()); +#endif + + return true; +} + +bool fs::dir::close() +{ + g_tls_error = fse::ok; + + if (!m_path) + { + return false; + } + + m_path.reset(); + +#ifdef _WIN32 + CHECK_ASSERTION(m_dd == -1 || FindClose((HANDLE)m_dd)); +#else + CHECK_ASSERTION(!::closedir((DIR*)m_dd)); +#endif + + return true; +} + +bool fs::dir::read(std::string& name, stat_t& info) +{ + g_tls_error = fse::ok; + + if (!m_path) + { + return false; + } + +#ifdef _WIN32 + WIN32_FIND_DATAW found; + + if (m_dd == -1) + { + m_dd = (std::intptr_t)FindFirstFileW(to_wchar(m_path.get() + std::string("/*")).get(), &found); + + if (m_dd == -1) + { + return false; + } + } + else if (!FindNextFileW((HANDLE)m_dd, &found)) + { + return false; + } + + to_utf8(name, found.cFileName); + + info.is_directory = (found.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + info.is_writable = (found.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0; + info.size = ((u64)found.nFileSizeHigh << 32) | (u64)found.nFileSizeLow; + info.atime = to_time(found.ftLastAccessTime); + info.mtime = to_time(found.ftLastWriteTime); + info.ctime = to_time(found.ftCreationTime); +#else + const auto found = ::readdir((DIR*)m_dd); + + struct stat file_info; + if (!found || ::fstatat(::dirfd((DIR*)m_dd), found->d_name, &file_info, 0) < 0) + { + return false; + } + + name = found->d_name; + + info.is_directory = S_ISDIR(file_info.st_mode); + info.is_writable = file_info.st_mode & 0200; // HACK: approximation + info.size = file_info.st_size; + info.atime = file_info.st_atime; + info.mtime = file_info.st_mtime; + info.ctime = file_info.st_ctime; +#endif + + return true; +} + +std::string fs::get_config_dir() +{ + // Use magic static for dir initialization + static const std::string s_dir = [] + { +#ifdef _WIN32 + return get_executable_dir(); // ? +#else + std::string dir; + + if (const char* home = ::getenv("XDG_CONFIG_HOME")) + dir = home; + else if (const char* home = ::getenv("HOME")) + dir = home + "/.config"s; + else // Just in case + dir = "./config"; + + dir += "/rpcs3/"; + + if (::mkdir(dir.c_str(), 0777) == -1 && errno != EEXIST) + { + std::printf("Failed to create configuration directory '%s' (%d).\n", dir.c_str(), errno); + } + + return dir; +#endif + }(); + + return s_dir; +} + +std::string fs::get_executable_dir() +{ + // Use magic static for dir initialization + static const std::string s_dir = [] + { + std::string dir; + +#ifdef _WIN32 + wchar_t buf[2048]; + if (GetModuleFileName(NULL, buf, ::size32(buf)) - 1 >= ::size32(buf) - 1) + { + MessageBoxA(0, fmt::format("GetModuleFileName() failed (0x%x).", GetLastError()).c_str(), "fs::get_config_dir()", MB_ICONERROR); + return dir; // empty + } + + to_utf8(dir, buf); // Convert to UTF-8 + +#elif __APPLE__ + char buf[4096]; + u32 size = sizeof(buf); + if (_NSGetExecutablePath(buf, &size)) + { + std::printf("_NSGetExecutablePath() failed (size=0x%x).\n", size); + return dir; // empty + } + + dir = buf; +#else + char buf[4096]; + const auto size = ::readlink("/proc/self/exe", buf, sizeof(buf)); + if (size <= 0 || size >= sizeof(buf)) + { + std::printf("readlink(/proc/self/exe) failed (%d).\n", errno); + return dir; // empty + } + + dir = { buf, static_cast(size) }; +#endif + + // Replace "\" + for (auto& c : dir) + { + if (c == '\\') c = '/'; + } + + // Leave only path + dir.resize(dir.rfind('/') + 1); + return dir; + }(); + + return s_dir; +} diff --git a/src/GNU.cpp b/src/GNU.cpp new file mode 100644 index 0000000..29467b2 --- /dev/null +++ b/src/GNU.cpp @@ -0,0 +1,51 @@ +#include "GNU.h" + +#ifdef __APPLE__ +#include +#include +#include +#include +#include +#undef CPU_STATE_MAX + +#define MT_NANO (+1.0E-9) +#define MT_GIGA UINT64_C(1000000000) + +// TODO create a list of timers, +static double mt_timebase = 0.0; +static uint64_t mt_timestart = 0; + +// TODO be more careful in a multithreaded environement +int clock_gettime(clockid_t clk_id, struct timespec *tp) +{ + kern_return_t retval = KERN_SUCCESS; + if( clk_id == TIMER_ABSTIME) + { + if (!mt_timestart) { // only one timer, initilized on the first call to the TIMER + mach_timebase_info_data_t tb = { 0 }; + mach_timebase_info(&tb); + mt_timebase = tb.numer; + mt_timebase /= tb.denom; + mt_timestart = mach_absolute_time(); + } + + double diff = (mach_absolute_time() - mt_timestart) * mt_timebase; + tp->tv_sec = diff * MT_NANO; + tp->tv_nsec = diff - (tp->tv_sec * MT_GIGA); + } + else // other clk_ids are mapped to the coresponding mach clock_service + { + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), clk_id, &cclock); + retval = clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + + tp->tv_sec = mts.tv_sec; + tp->tv_nsec = mts.tv_nsec; + } + + return retval; +} +#endif /* __APPLE__ */ diff --git a/src/Log.cpp b/src/Log.cpp new file mode 100644 index 0000000..60ce1dc --- /dev/null +++ b/src/Log.cpp @@ -0,0 +1,280 @@ +#include +#include +#include "File.h" +#include "Log.h" + +#ifdef _WIN32 +#include +#endif + +using namespace Log; + +std::unique_ptr g_log_manager; + +u32 LogMessage::size() const +{ + //1 byte for NULL terminator + return (u32)(sizeof(LogMessage::size_type) + sizeof(LogType) + sizeof(Severity) + sizeof(std::string::value_type) * mText.size() + 1); +} + +void LogMessage::serialize(char *output) const +{ + LogMessage::size_type size = this->size(); + memcpy(output, &size, sizeof(LogMessage::size_type)); + output += sizeof(LogMessage::size_type); + memcpy(output, &mType, sizeof(LogType)); + output += sizeof(LogType); + memcpy(output, &mServerity, sizeof(Severity)); + output += sizeof(Severity); + memcpy(output, mText.c_str(), mText.size() ); + output += sizeof(std::string::value_type)*mText.size(); + *output = '\0'; + +} +LogMessage LogMessage::deserialize(char *input, u32* size_out) +{ + LogMessage msg; + LogMessage::size_type msgSize = *(reinterpret_cast(input)); + input += sizeof(LogMessage::size_type); + msg.mType = *(reinterpret_cast(input)); + input += sizeof(LogType); + msg.mServerity = *(reinterpret_cast(input)); + input += sizeof(Severity); + if (msgSize > 9000) + { + int wtf = 6; + } + msg.mText.append(input, msgSize - 1 - sizeof(Severity) - sizeof(LogType)); + if (size_out){(*size_out) = msgSize;} + return msg; +} + + + +LogChannel::LogChannel() : LogChannel("unknown") +{} + +LogChannel::LogChannel(const std::string& name) : + name(name) + , mEnabled(true) + , mLogLevel(Severity::Warning) +{} + +void LogChannel::log(const LogMessage &msg) +{ + std::lock_guard lock(mListenerLock); + for (auto &listener : mListeners) + { + listener->log(msg); + } +} + +void LogChannel::addListener(std::shared_ptr listener) +{ + std::lock_guard lock(mListenerLock); + mListeners.insert(listener); +} +void LogChannel::removeListener(std::shared_ptr listener) +{ + std::lock_guard lock(mListenerLock); + mListeners.erase(listener); +} + +struct CoutListener : LogListener +{ + void log(const LogMessage &msg) override + { + std::cerr << msg.mText << std::endl; + } +}; + +struct FileListener : LogListener +{ + fs::file mFile; + bool mPrependChannelName; + + FileListener(const std::string& name, bool prependChannel = true) + : mFile(fs::get_config_dir() + name, fom::rewrite) + , mPrependChannelName(prependChannel) + { + if (!mFile) + { +#ifdef _WIN32 + MessageBoxA(0, ("Can't create log file: " + name).c_str(), "Error", MB_ICONERROR); +#else + std::printf("Can't create log file: %s\n", name.c_str()); +#endif + } + } + + void log(const LogMessage &msg) override + { + std::string text = msg.mText; + if (mPrependChannelName) + { + text.insert(0, gTypeNameTable[static_cast(msg.mType)].mName); + + if (msg.mType == Log::TTY) + { + text = fmt::escape(text); + if (text[text.length() - 1] != '\n') + { + text += '\n'; + } + } + } + + mFile << text; + } +}; + +LogManager::LogManager() +#ifdef BUFFERED_LOGGING + : mExiting(false), mLogConsumer() +#endif +{ + auto it = mChannels.begin(); + std::shared_ptr listener(new FileListener("program.log")); + for (const LogTypeName& name : gTypeNameTable) + { + it->name = name.mName; + it->addListener(listener); + it++; + } + std::shared_ptr TTYListener(new FileListener("TTY.log", false)); + getChannel(TTY).addListener(TTYListener); +#ifdef BUFFERED_LOGGING + mLogConsumer = std::thread(&LogManager::consumeLog, this); +#endif +} + +LogManager::~LogManager() +{ +#ifdef BUFFERED_LOGGING + mExiting = true; + mBufferReady.notify_all(); + mLogConsumer.join(); +} + +void LogManager::consumeLog() +{ + std::unique_lock lock(mStatusMut); + while (!mExiting) + { + mBufferReady.wait(lock); + mBuffer.lockGet(); + size_t size = mBuffer.size(); + std::vector local_messages(size); + mBuffer.popN(&local_messages.front(), size); + mBuffer.unlockGet(); + + u32 cursor = 0; + u32 removed = 0; + while (cursor < size) + { + Log::LogMessage msg = Log::LogMessage::deserialize(local_messages.data() + cursor, &removed); + cursor += removed; + getChannel(msg.mType).log(msg); + } + } +#endif +} + +void LogManager::log(LogMessage msg) +{ + //don't do any formatting changes or filtering to the TTY output since we + //use the raw output to do diffs with the output of a real PS3 and some + //programs write text in single bytes to the console + if (msg.mType != TTY) + { + std::string prefix; + switch (msg.mServerity) + { + case Severity::Success: + prefix = "S "; + break; + case Severity::Notice: + prefix = "! "; + break; + case Severity::Warning: + prefix = "W "; + break; + case Severity::Error: + prefix = "E "; + break; + } + + msg.mText.insert(0, prefix); + msg.mText.append(1,'\n'); + } +#ifdef BUFFERED_LOGGING + size_t size = msg.size(); + std::vector temp_buffer(size); + msg.serialize(temp_buffer.data()); + mBuffer.pushRange(temp_buffer.begin(), temp_buffer.end()); + mBufferReady.notify_one(); +#else + mChannels[static_cast(msg.mType)].log(msg); +#endif +} + +void LogManager::addListener(std::shared_ptr listener) +{ + for (auto& channel : mChannels) + { + channel.addListener(listener); + } +} + +void LogManager::removeListener(std::shared_ptr listener) +{ + for (auto& channel : mChannels) + { + channel.removeListener(listener); + } +} + +LogManager& LogManager::getInstance() +{ + if (!g_log_manager) + { + g_log_manager.reset(new LogManager()); + } + + return *g_log_manager; +} + +LogChannel &LogManager::getChannel(LogType type) +{ + return mChannels[static_cast(type)]; +} + +void log_message(Log::LogType type, Log::Severity sev, const char* text) +{ + log_message(type, sev, std::string(text)); +} + +void log_message(Log::LogType type, Log::Severity sev, std::string text) +{ + if (g_log_manager) + { + g_log_manager->log({ type, sev, std::move(text) }); + } + else + { + const auto severity = + sev == Severity::Notice ? "Notice" : + sev == Severity::Warning ? "Warning" : + sev == Severity::Success ? "Success" : + sev == Severity::Error ? "Error" : "Unknown"; + +#ifdef _WIN32 + MessageBoxA(0, text.c_str(), severity, + sev == Severity::Notice ? MB_ICONINFORMATION : + sev == Severity::Warning ? MB_ICONEXCLAMATION : + sev == Severity::Error ? MB_ICONERROR : MB_ICONINFORMATION); +#else + std::printf("[Log:%s] %s\n", severity, text.c_str()); +#endif + } +} diff --git a/src/Semaphore.cpp b/src/Semaphore.cpp new file mode 100644 index 0000000..91b81e0 --- /dev/null +++ b/src/Semaphore.cpp @@ -0,0 +1,119 @@ +#include "Semaphore.h" + +bool semaphore_t::try_wait() +{ + // check m_value without interlocked op + if (m_var.load().value == 0) + { + return false; + } + + // try to decrement m_value atomically + const auto old = m_var.atomic_op([](sync_var_t& var) + { + if (var.value) + { + var.value--; + } + }); + + // recheck atomic result + if (old.value == 0) + { + return false; + } + + return true; +} + +bool semaphore_t::try_post() +{ + // check m_value without interlocked op + if (m_var.load().value >= max_value) + { + return false; + } + + // try to increment m_value atomically + const auto old = m_var.atomic_op([&](sync_var_t& var) + { + if (var.value < max_value) + { + var.value++; + } + }); + + // recheck atomic result + if (old.value >= max_value) + { + return false; + } + + if (old.waiters) + { + // notify waiting thread + std::lock_guard lock(m_mutex); + + m_cv.notify_one(); + } + + return true; +} + +void semaphore_t::wait() +{ + if (m_var.atomic_op([](sync_var_t& var) -> bool + { + if (var.value) + { + var.value--; + + return true; + } + else + { + //var.waiters++; + + return false; + } + })) + { + return; + } + + std::unique_lock lock(m_mutex); + + m_var.atomic_op([](sync_var_t& var) + { + var.waiters++; + }); + + while (!m_var.atomic_op([](sync_var_t& var) -> bool + { + if (var.value) + { + var.value--; + var.waiters--; + + return true; + } + else + { + return false; + } + })) + { + m_cv.wait(lock); + } +} + +bool semaphore_t::post_and_wait() +{ + // TODO: merge these functions? Probably has a race condition. + if (try_wait()) return false; + + try_post(); + wait(); + + return true; +} diff --git a/src/SharedMutex.cpp b/src/SharedMutex.cpp new file mode 100644 index 0000000..72bca61 --- /dev/null +++ b/src/SharedMutex.cpp @@ -0,0 +1,103 @@ +#include "SharedMutex.h" +#include "StrFmt.h" + +void shared_mutex::impl_lock_shared(u32 old_value) +{ + // Throw if reader count breaks the "second" limit (it should be impossible) + CHECK_ASSERTION((old_value & SM_READER_COUNT) != SM_READER_COUNT); + + std::unique_lock lock(m_mutex); + + // Notify non-zero reader queue size + m_ctrl |= SM_READER_QUEUE; + + // Compensate incorrectly increased reader count + if ((--m_ctrl & SM_READER_COUNT) == 0 && m_wq_size) + { + // Notify current exclusive owner (condition passed) + m_ocv.notify_one(); + } + + CHECK_ASSERTION(++m_rq_size); + + // Obtain the reader lock + while (!atomic_op(m_ctrl, op_lock_shared)) + { + m_rcv.wait(lock); + } + + CHECK_ASSERTION(m_rq_size--); + + if (m_rq_size == 0) + { + m_ctrl &= ~SM_READER_QUEUE; + } +} + +void shared_mutex::impl_unlock_shared(u32 new_value) +{ + // Throw if reader count was zero + CHECK_ASSERTION((new_value & SM_READER_COUNT) != SM_READER_COUNT); + + // Mutex cannot be unlocked before notification because m_ctrl has been changed outside + std::lock_guard lock(m_mutex); + + if (m_wq_size && (new_value & SM_READER_COUNT) == 0) + { + // Notify current exclusive owner that the latest reader is gone + m_ocv.notify_one(); + } + else if (m_rq_size) + { + m_rcv.notify_one(); + } +} + +void shared_mutex::impl_lock_excl(u32 value) +{ + std::unique_lock lock(m_mutex); + + // Notify non-zero writer queue size + m_ctrl |= SM_WRITER_QUEUE; + + CHECK_ASSERTION(++m_wq_size); + + // Obtain the writer lock + while (!atomic_op(m_ctrl, op_lock_excl)) + { + m_wcv.wait(lock); + } + + // Wait for remaining readers + while ((m_ctrl & SM_READER_COUNT) != 0) + { + m_ocv.wait(lock); + } + + CHECK_ASSERTION(m_wq_size--); + + if (m_wq_size == 0) + { + m_ctrl &= ~SM_WRITER_QUEUE; + } +} + +void shared_mutex::impl_unlock_excl(u32 value) +{ + // Throw if was not locked exclusively + CHECK_ASSERTION(value & SM_WRITER_LOCK); + + // Mutex cannot be unlocked before notification because m_ctrl has been changed outside + std::lock_guard lock(m_mutex); + + if (m_wq_size) + { + // Notify next exclusive owner + m_wcv.notify_one(); + } + else if (m_rq_size) + { + // Notify all readers + m_rcv.notify_all(); + } +} diff --git a/src/SleepQueue.cpp b/src/SleepQueue.cpp new file mode 100644 index 0000000..a04cd95 --- /dev/null +++ b/src/SleepQueue.cpp @@ -0,0 +1,52 @@ +#include "SleepQueue.h" + +void sleep_queue_entry_t::add_entry() +{ + m_queue.emplace_back(std::static_pointer_cast(m_thread.shared_from_this())); +} + +void sleep_queue_entry_t::remove_entry() +{ + for (auto it = m_queue.begin(); it != m_queue.end(); it++) + { + if (it->get() == &m_thread) + { + m_queue.erase(it); + return; + } + } +} + +bool sleep_queue_entry_t::find() const +{ + for (auto it = m_queue.begin(); it != m_queue.end(); it++) + { + if (it->get() == &m_thread) + { + return true; + } + } + + return false; +} + +sleep_queue_entry_t::sleep_queue_entry_t(sleep_entry_t& cpu, sleep_queue_t& queue) + : m_thread(cpu) + , m_queue(queue) +{ + add_entry(); + cpu.sleep(); +} + +sleep_queue_entry_t::sleep_queue_entry_t(sleep_entry_t& cpu, sleep_queue_t& queue, const defer_sleep_t&) + : m_thread(cpu) + , m_queue(queue) +{ + cpu.sleep(); +} + +sleep_queue_entry_t::~sleep_queue_entry_t() +{ + remove_entry(); + m_thread.awake(); +} diff --git a/src/StrFmt.cpp b/src/StrFmt.cpp new file mode 100644 index 0000000..0f490e5 --- /dev/null +++ b/src/StrFmt.cpp @@ -0,0 +1,239 @@ +#include "StrFmt.h" + +std::string v128::to_hex() const +{ + return fmt::format("%016llx%016llx", _u64[1], _u64[0]); +} + +std::string v128::to_xyzw() const +{ + return fmt::format("x: %g y: %g z: %g w: %g", _f[3], _f[2], _f[1], _f[0]); +} + +std::string fmt::to_hex(u64 value, u64 count) +{ + if (count - 1 >= 16) + { + throw EXCEPTION("Invalid count: 0x%llx", count); + } + + count = std::max(count, 16 - cntlz64(value) / 4); + + char res[16] = {}; + + for (size_t i = count - 1; ~i; i--, value /= 16) + { + res[i] = "0123456789abcdef"[value % 16]; + } + + return std::string(res, count); +} + +std::string fmt::to_udec(u64 value) +{ + char res[20] = {}; + size_t first = sizeof(res); + + if (!value) + { + res[--first] = '0'; + } + + for (; value; value /= 10) + { + res[--first] = '0' + (value % 10); + } + + return std::string(&res[first], sizeof(res) - first); +} + +std::string fmt::to_sdec(s64 svalue) +{ + const bool sign = svalue < 0; + u64 value = sign ? -svalue : svalue; + + char res[20] = {}; + size_t first = sizeof(res); + + if (!value) + { + res[--first] = '0'; + } + + for (; value; value /= 10) + { + res[--first] = '0' + (value % 10); + } + + if (sign) + { + res[--first] = '-'; + } + + return std::string(&res[first], sizeof(res) - first); +} + +//extern const std::string fmt::placeholder = "???"; + +std::string fmt::replace_first(const std::string& src, const std::string& from, const std::string& to) +{ + auto pos = src.find(from); + + if (pos == std::string::npos) + { + return src; + } + + return (pos ? src.substr(0, pos) + to : to) + std::string(src.c_str() + pos + from.length()); +} + +std::string fmt::replace_all(const std::string &src, const std::string& from, const std::string& to) +{ + std::string target = src; + for (auto pos = target.find(from); pos != std::string::npos; pos = target.find(from, pos + 1)) + { + target = (pos ? target.substr(0, pos) + to : to) + std::string(target.c_str() + pos + from.length()); + pos += to.length(); + } + + return target; +} + +//TODO: remove this after every snippet that uses it is gone +//WARNING: not fully compatible with CmpNoCase from wxString +int fmt::CmpNoCase(const std::string& a, const std::string& b) +{ + if (a.length() != b.length()) + { + return -1; + } + else + { + return std::equal(a.begin(), + a.end(), + b.begin(), + [](const char& a, const char& b){return ::tolower(a) == ::tolower(b); }) + ? 0 : -1; + } +} + +//TODO: remove this after every snippet that uses it is gone +//WARNING: not fully compatible with CmpNoCase from wxString +void fmt::Replace(std::string &str, const std::string &searchterm, const std::string& replaceterm) +{ + size_t cursor = 0; + do + { + cursor = str.find(searchterm, cursor); + if (cursor != std::string::npos) + { + str.replace(cursor, searchterm.size(), replaceterm); + cursor += replaceterm.size(); + } + else + { + break; + } + } while (true); +} + +std::vector fmt::rSplit(const std::string& source, const std::string& delim) +{ + std::vector ret; + size_t cursor = 0; + do + { + size_t prevcurs = cursor; + cursor = source.find(delim, cursor); + if (cursor != std::string::npos) + { + ret.push_back(source.substr(prevcurs,cursor-prevcurs)); + cursor += delim.size(); + } + else + { + ret.push_back(source.substr(prevcurs)); + break; + } + } while (true); + return ret; +} + +std::vector fmt::split(const std::string& source, std::initializer_list separators, bool is_skip_empty) +{ + std::vector result; + + size_t cursor_begin = 0; + + for (size_t cursor_end = 0; cursor_end < source.length(); ++cursor_end) + { + for (auto &separator : separators) + { + if (strncmp(source.c_str() + cursor_end, separator.c_str(), separator.length()) == 0) + { + std::string candidate = source.substr(cursor_begin, cursor_end - cursor_begin); + if (!is_skip_empty || !candidate.empty()) + result.push_back(candidate); + + cursor_begin = cursor_end + separator.length(); + cursor_end = cursor_begin - 1; + break; + } + } + } + + if (cursor_begin != source.length()) + { + result.push_back(source.substr(cursor_begin)); + } + + return std::move(result); +} + +std::string fmt::trim(const std::string& source, const std::string& values) +{ + std::size_t begin = source.find_first_not_of(values); + + if (begin == source.npos) + return{}; + + return source.substr(begin, source.find_last_not_of(values) + 1); +} + +std::string fmt::tolower(std::string source) +{ + std::transform(source.begin(), source.end(), source.begin(), ::tolower); + + return source; +} + +std::string fmt::toupper(std::string source) +{ + std::transform(source.begin(), source.end(), source.begin(), ::toupper); + + return source; +} + +std::string fmt::escape(std::string source) +{ + const std::pair escape_list[] = + { + { "\\", "\\\\" }, + { "\a", "\\a" }, + { "\b", "\\b" }, + { "\f", "\\f" }, + { "\n", "\\n\n" }, + { "\r", "\\r" }, + { "\t", "\\t" }, + { "\v", "\\v" }, + }; + + source = fmt::replace_all(source, escape_list); + + for (char c = 0; c < 32; c++) + { + if (c != '\n') source = fmt::replace_all(source, std::string(1, c), fmt::format("\\x%02X", c)); + } + + return source; +} diff --git a/src/VirtualMemory.cpp b/src/VirtualMemory.cpp new file mode 100644 index 0000000..6052995 --- /dev/null +++ b/src/VirtualMemory.cpp @@ -0,0 +1,47 @@ +#include "Log.h" +#include "VirtualMemory.h" +#include "GNU.h" +#include "StrFmt.h" + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#endif + +namespace memory_helper +{ + void* reserve_memory(size_t size) + { +#ifdef _WIN32 + void* ret = VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_NOACCESS); + CHECK_ASSERTION(ret != NULL); +#else + void* ret = mmap(nullptr, size, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0); + CHECK_ASSERTION(ret != 0); +#endif + return ret; + } + + void commit_page_memory(void* pointer, size_t page_size) + { +#ifdef _WIN32 + CHECK_ASSERTION(VirtualAlloc((u8*)pointer, page_size, MEM_COMMIT, PAGE_READWRITE) != NULL); +#else + CHECK_ASSERTION(mprotect((u8*)pointer, page_size, PROT_READ | PROT_WRITE) != -1); +#endif + } + + void free_reserved_memory(void* pointer, size_t size) + { +#ifdef _WIN32 + CHECK_ASSERTION(VirtualFree(pointer, 0, MEM_RELEASE) != 0); +#else + CHECK_ASSERTION(munmap(pointer, size) == 0); +#endif + } +} diff --git a/src/config_context.cpp b/src/config_context.cpp new file mode 100644 index 0000000..5b1c5c4 --- /dev/null +++ b/src/config_context.cpp @@ -0,0 +1,171 @@ +#include "config_context.h" +#include "StrFmt.h" +#include +#include + +void config_context_t::group::init() +{ + if(!m_cfg->m_groups[full_name()]) + m_cfg->m_groups[full_name()] = this; +} + +config_context_t::group::group(config_context_t* cfg, const std::string& name) + : m_cfg(cfg) + , m_name(name) + , m_parent(nullptr) +{ + init(); +} + +config_context_t::group::group(group* parent, const std::string& name) + : m_cfg(parent->m_cfg) + , m_name(name) + , m_parent(parent) +{ + init(); +} + +void config_context_t::group::set_parent(config_context_t* cfg) +{ + m_cfg = cfg; + init(); +} + +std::string config_context_t::group::name() const +{ + return m_name; +} + +std::string config_context_t::group::full_name() const +{ + if (m_parent) + return m_parent->full_name() + "/" + m_name; + + return m_name; +} + +void config_context_t::assign(const config_context_t& rhs) +{ + for (auto &rhs_g : rhs.m_groups) + { + auto g = m_groups.at(rhs_g.first); + + for (auto rhs_e : rhs_g.second->entries) + { + if (g->entries[rhs_e.first]) + g->entries[rhs_e.first]->value_from(rhs_e.second); + else + g->add_entry(rhs_e.first, rhs_e.second->string_value()); + } + } +} + +void config_context_t::deserialize(std::istream& stream) +{ + set_defaults(); + + uint line_index = 0; + std::string line; + group *current_group = nullptr; + + while (std::getline(stream, line)) + { + ++line_index; + line = fmt::trim(line); + + if (line.empty()) + continue; + + if (line.front() == '[' && line.back() == ']') + { + std::string group_name = line.substr(1, line.length() - 2); + + auto found = m_groups.find(group_name); + + if (found == m_groups.end()) + { + std::cerr << line_index << ": group '" << group_name << "' not exists. ignored" << std::endl; + current_group = nullptr; + continue; + } + + current_group = found->second; + continue; + } + + if (current_group == nullptr) + { + std::cerr << line_index << ": line '" << line << "' ignored, no group." << std::endl; + continue; + } + + auto name_value = fmt::split(line, { "=" }); + switch (name_value.size()) + { + case 1: + { + if (current_group->entries[fmt::trim(name_value[0])]) + current_group->entries[fmt::trim(name_value[0])]->string_value({}); + + else + current_group->add_entry(fmt::trim(name_value[0]), std::string{}); + } + break; + + default: + std::cerr << line_index << ": line '" << line << "' has more than one symbol '='. used only first" << std::endl; + case 2: + { + if (current_group->entries[fmt::trim(name_value[0])]) + current_group->entries[fmt::trim(name_value[0])]->string_value(fmt::trim(name_value[1])); + + else + current_group->add_entry(fmt::trim(name_value[0]), fmt::trim(name_value[1])); + } + break; + + } + } +} + +void config_context_t::serialize(std::ostream& stream) const +{ + for (auto &g : m_groups) + { + stream << "[" + g.first + "]" << std::endl; + + for (auto &e : g.second->entries) + { + stream << e.first << "=" << e.second->string_value() << std::endl; + } + + stream << std::endl; + } +} + +void config_context_t::set_defaults() +{ + for (auto &g : m_groups) + { + for (auto &e : g.second->entries) + { + e.second->to_default(); + } + } +} + +std::string config_context_t::to_string() const +{ + std::ostringstream result; + + serialize(result); + + return result.str(); +} + +void config_context_t::from_string(const std::string& str) +{ + std::istringstream source(str); + + deserialize(source); +}