diff --git a/Utilities/Atomic.h b/Utilities/Atomic.h index 49948c27446a..6dcb92fd5d4f 100644 --- a/Utilities/Atomic.h +++ b/Utilities/Atomic.h @@ -1,43 +1,57 @@ #pragma once +#include + +#include "Platform.h" +#include "Macro.h" +#include "types.h" + #if defined(__GNUG__) -template inline std::enable_if_t sync_val_compare_and_swap(volatile T* dest, T2 comp, T2 exch) +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) +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) +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) +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) +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) +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) +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) +template +inline std::enable_if_t sync_fetch_and_xor(volatile T* dest, T2 value) { return __sync_fetch_and_xor(dest, value); } @@ -303,32 +317,38 @@ inline u128 sync_fetch_and_xor(volatile u128* dest, u128 value) #endif /* _MSC_VER */ -template struct atomic_storage +template +struct atomic_storage { static_assert(!Size, "Invalid atomic type"); }; -template struct atomic_storage +template +struct atomic_storage { using type = u8; }; -template struct atomic_storage +template +struct atomic_storage { using type = u16; }; -template struct atomic_storage +template +struct atomic_storage { using type = u32; }; -template struct atomic_storage +template +struct atomic_storage { using type = u64; }; -template struct atomic_storage +template +struct atomic_storage { using type = u128; }; @@ -336,7 +356,8 @@ template struct atomic_storage 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 +template +struct atomic_op_result_t { RT result; @@ -352,7 +373,8 @@ template struct atomic_op_result_t }; // void specialization: result is the initial value of the first arg -template struct atomic_op_result_t +template +struct atomic_op_result_t { VT result; @@ -369,7 +391,8 @@ template struct atomic_op_result_t }; // member function specialization -template struct atomic_op_result_t +template +struct atomic_op_result_t { RT result; @@ -385,7 +408,8 @@ template struct atomic }; // member function void specialization -template struct atomic_op_result_t +template +struct atomic_op_result_t { VT result; @@ -402,7 +426,8 @@ template struct atomic_op_result_t< }; // Atomic type with lock-free and standard layout guarantees (and appropriate limitations) -template class atomic_t +template +class atomic_t { using type = std::remove_cv_t; using stype = atomic_storage_t; @@ -508,7 +533,8 @@ template class atomic_t // 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) + template> + auto atomic_op(F func, Args&&... args) volatile -> decltype(atomic_op_result_t::result) { while (true) { @@ -566,122 +592,47 @@ template class atomic_t } }; -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) +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) +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) +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) +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) +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, nse_t> operator -=(atomic_t>& left, const T2& 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.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>; +#include // 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) +template> +auto atomic_op(std::atomic& var, F func, Args&&... args) -> decltype(atomic_op_result_t::result) { auto old = var.load(); diff --git a/Utilities/AutoPause.cpp b/Utilities/AutoPause.cpp index 35608d33005b..f2959c3cfe33 100644 --- a/Utilities/AutoPause.cpp +++ b/Utilities/AutoPause.cpp @@ -1,57 +1,29 @@ #include "stdafx.h" -#include "AutoPause.h" -#include "Utilities/Log.h" -#include "Utilities/File.h" +#include "Registry.h" #include "Emu/System.h" -#include "Emu/state.h" - -using namespace Debug; +#include "AutoPause.h" -std::unique_ptr g_autopause; +const extern cfg::bool_entry g_cfg_debug_autopause_syscall("misc/debug/Auto Pause at System Call"); +const extern cfg::bool_entry g_cfg_debug_autopause_func_call("misc/debug/Auto Pause at Function Call"); -AutoPause& AutoPause::getInstance(void) +debug::autopause& debug::autopause::get_instance() { - if (!g_autopause) - { - g_autopause.reset(new AutoPause); - } - - return *g_autopause; + // Use magic static + static autopause instance; + return instance; } -//Still use binary format. Default Setting should be "disable all auto pause". -AutoPause::AutoPause(void) +// Load Auto Pause Configuration from file "pause.bin" +void debug::autopause::reload(void) { - m_pause_function.reserve(16); - m_pause_syscall.reserve(16); - initialized = false; - //Reload(false, false); - Reload(); -} + auto& instance = get_instance(); -//Notice: I would not allow to write the binary to file in this command. -AutoPause::~AutoPause(void) -{ - initialized = false; - m_pause_function.clear(); - m_pause_syscall.clear(); - m_pause_function_enable = false; - m_pause_syscall_enable = false; -} + instance.m_pause_function.clear(); + instance.m_pause_syscall.clear(); -//Load Auto Pause Configuration from file "pause.bin" -//This would be able to create in a GUI window. -void AutoPause::Reload(void) -{ - if (fs::is_file(fs::get_config_dir() + "pause.bin")) + // TODO: better format, possibly a config entry + if (fs::file list{ fs::get_config_dir() + "pause.bin" }) { - m_pause_function.clear(); - m_pause_function.reserve(16); - m_pause_syscall.clear(); - m_pause_syscall.reserve(16); - - fs::file list(fs::get_config_dir() + "pause.bin"); - //System calls ID and Function calls ID are all u32 iirc. u32 num; size_t fmax = list.size(); size_t fcur = 0; @@ -64,60 +36,32 @@ void AutoPause::Reload(void) if (num < 1024) { - //Less than 1024 - be regarded as a system call. - //emplace_back may not cause reductant move/copy operation. - m_pause_syscall.emplace_back(num); - LOG_WARNING(HLE, "Auto Pause: Find System Call ID 0x%x", num); + instance.m_pause_syscall.emplace(num); + LOG_WARNING(HLE, "Set autopause at syscall %lld", num); } else { - m_pause_function.emplace_back(num); - LOG_WARNING(HLE, "Auto Pause: Find Function Call ID 0x%x", num); + instance.m_pause_function.emplace(num); + LOG_WARNING(HLE, "Set autopause at function 0x%08x", num); } } } - - m_pause_syscall_enable = rpcs3::config.misc.debug.auto_pause_syscall.value(); - m_pause_function_enable = rpcs3::config.misc.debug.auto_pause_func_call.value(); - initialized = true; } -void AutoPause::TryPause(u32 code) +void debug::autopause::pause_syscall(u64 code) { - if (code < 1024) + if (g_cfg_debug_autopause_syscall && get_instance().m_pause_syscall.count(code) != 0) { - //Would first check Enable setting. Then the list length. - if ((!m_pause_syscall_enable) - || (m_pause_syscall.size() <= 0)) - { - return; - } - - for (u32 i = 0; i < m_pause_syscall.size(); ++i) - { - if (code == m_pause_syscall[i]) - { - Emu.Pause(); - LOG_ERROR(HLE, "Auto Pause Triggered: System call 0x%x", code); // Used Error - } - } + Emu.Pause(); + LOG_SUCCESS(HLE, "Autopause triggered at syscall %lld", code); } - else - { - //Well similiar.. Seperate the list caused by possible setting difference. - if ((!m_pause_function_enable) - || (m_pause_function.size() <= 0)) - { - return; - } +} - for (u32 i = 0; i < m_pause_function.size(); ++i) - { - if (code == m_pause_function[i]) - { - Emu.Pause(); - LOG_ERROR(HLE, "Auto Pause Triggered: Function call 0x%x", code); // Used Error - } - } +void debug::autopause::pause_function(u32 code) +{ + if (g_cfg_debug_autopause_func_call && get_instance().m_pause_function.count(code) != 0) + { + Emu.Pause(); + LOG_SUCCESS(HLE, "Autopause triggered at function 0x%08x", code); } } diff --git a/Utilities/AutoPause.h b/Utilities/AutoPause.h index cf871b46cb55..b06975dfe6fc 100644 --- a/Utilities/AutoPause.h +++ b/Utilities/AutoPause.h @@ -1,24 +1,20 @@ #pragma once -//Regarded as a Debugger Enchantment -namespace Debug { - //To store the pause function/call id, and let those pause there. - //Would be with a GUI to configure those. - struct AutoPause +// Regarded as a Debugger Enchantment +namespace debug +{ + // To store the pause function/call id, and let those pause there. + // Would be with a GUI to configure those. + class autopause { - std::vector m_pause_syscall; - std::vector m_pause_function; - bool initialized; - bool m_pause_syscall_enable; - bool m_pause_function_enable; + std::unordered_set m_pause_syscall; + std::unordered_set m_pause_function; - AutoPause(); - ~AutoPause(); + static autopause& get_instance(); public: - static AutoPause& getInstance(void); - void Reload(void); - - void TryPause(u32 code); + static void reload(); + static void pause_syscall(u64 code); + static void pause_function(u32 code); }; -} \ No newline at end of file +} diff --git a/Utilities/BEType.h b/Utilities/BEType.h index 80c8256a2f7c..6368bf10edd2 100644 --- a/Utilities/BEType.h +++ b/Utilities/BEType.h @@ -1,12 +1,16 @@ #pragma once +#define IS_LE_MACHINE + #ifdef _MSC_VER #include #else #include #endif -#define IS_LE_MACHINE // only draft +#include "StrFmt.h" +#include "Atomic.h" +#include "Macro.h" union v128 { @@ -938,3 +942,152 @@ template struct to_ne // restore native endianness for T: returns T for be_t or le_t, T otherwise template using to_ne_t = typename to_ne::type; + +namespace fmt +{ + template + struct unveil> + { + using result_type = typename unveil::result_type; + + force_inline static result_type get_value(const se_t& arg) + { + return unveil::get_value(arg); + } + }; +} + +// Convert 1-byte string to u8 value +constexpr inline u8 operator""_u8(const char* str, std::size_t length) +{ + return length != 1 ? throw std::length_error(str) : static_cast(str[0]); +} + +// Convert 2-byte string to u16 value in native endianness +constexpr inline nse_t operator""_u16(const char* str, std::size_t length) +{ + return length != 2 ? throw std::length_error(str) : +#ifdef IS_LE_MACHINE + (static_cast(str[1]) << 8) | static_cast(str[0]); +#else + (static_cast(str[0]) << 8) | static_cast(str[1]); +#endif +} + +// Convert 4-byte string to u32 value in native endianness +constexpr inline nse_t operator""_u32(const char* str, std::size_t length) +{ + return length != 4 ? throw std::length_error(str) : +#ifdef IS_LE_MACHINE + (static_cast(str[3]) << 24) | (static_cast(str[2]) << 16) | (static_cast(str[1]) << 8) | static_cast(str[0]); +#else + (static_cast(str[0]) << 24) | (static_cast(str[1]) << 16) | (static_cast(str[2]) << 8) | static_cast(str[3]); +#endif +} + +// Convert 8-byte string to u64 value in native endianness (constexpr fails on gcc) +/*constexpr*/ inline nse_t operator""_u64(const char* str, std::size_t length) +{ + return length != 8 ? throw std::length_error(str) : +#ifdef IS_LE_MACHINE + ::operator""_u32(str, 4) | (static_cast(::operator""_u32(str + 4, 4)) << 32); +#else + ::operator""_u32(str + 4, 4) | (static_cast(::operator""_u32(str, 4)) << 32); +#endif +} + +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>; diff --git a/Utilities/BitField.h b/Utilities/BitField.h index 7c7ad0704f04..d96114b9708e 100644 --- a/Utilities/BitField.h +++ b/Utilities/BitField.h @@ -1,7 +1,11 @@ #pragma once +#include "types.h" +#include + // BitField access helper class (N bits from I position), intended to be put in union -template class bf_t +template +class bf_t { // Checks static_assert(I < sizeof(T) * 8, "bf_t<> error: I out of bounds"); @@ -21,9 +25,11 @@ template class bf_t type m_data; // Conversion operator helper (uses SFINAE) - template struct converter {}; + template + struct converter {}; - template struct converter::value>> + template + struct converter::value>> { // Load unsigned value static inline T2 convert(const type& data) @@ -32,7 +38,8 @@ template class bf_t } }; - template struct converter::value>> + template + struct converter::value>> { // Load signed value (sign-extended) static inline T2 convert(const type& data) @@ -135,6 +142,8 @@ template class bf_t } }; +#include "BEType.h" + template using bf_be_t = bf_t, I, N>; template using bf_le_t = bf_t, I, N>; diff --git a/Utilities/Const.h b/Utilities/Const.h new file mode 100644 index 000000000000..1ab4d78f22d5 --- /dev/null +++ b/Utilities/Const.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +template::value>> +constexpr inline T align(const T& value, std::uint64_t align) +{ + return static_cast((value + (align - 1)) & ~(align - 1)); +} + +// 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; +} + +// Narrow cast (similar to gsl::narrow) with exception message formatting +template +inline auto narrow(const From& value, const char* format_str/* = "narrow() failed"*/, const Args&... args) -> decltype(static_cast(static_cast(std::declval()))) +{ + const auto result = static_cast(value); + if (static_cast(result) != value) throw fmt::exception(format_str, fmt::do_unveil(args)...); + return result; +} + +// Narrow cast (similar to gsl::narrow) with fixed message (workaround, because default argument fails to compile) +template +inline auto narrow(const From& value) -> decltype(static_cast(static_cast(std::declval()))) +{ + const auto result = static_cast(value); + if (static_cast(result) != value) throw std::runtime_error("narrow() failed"); + return result; +} + +// return 32 bit .size() for container +template +inline auto size32(const CT& container) -> decltype(static_cast(container.size())) +{ + return ::narrow(container.size(), "size32() failed"); +} + +// return 32 bit size for an array +template +constexpr std::uint32_t size32(const T(&)[Size]) +{ + static_assert(Size <= UINT32_MAX, "size32() error: too big"); + return static_cast(Size); +} + +// 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); +} diff --git a/Utilities/Enum.h b/Utilities/Enum.h new file mode 100644 index 000000000000..8b205aeb7b4f --- /dev/null +++ b/Utilities/Enum.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "Macro.h" + +// Bijective mapping between values of enum type T and arbitrary mappable type VT (std::string by default) +template +struct enum_map +{ + static_assert(std::is_enum::value, "enum_map<> error: invalid type T (must be enum)"); + + // Custom hasher provided for two reasons: + // 1) To simplify lookup algorithm for MSVC, make it similar everywhere. + // 2) Workaround for libstdc++ which fails on enum class types. + struct hash + { + std::size_t operator()(T value) const + { + return static_cast(value); + } + }; + + using init_type = std::initializer_list>; + using map_type = std::unordered_map; + using rmap_type = std::unordered_map; + using list_type = std::vector; + + static map_type make_map(init_type init) + { + map_type map(init.size()); map.reserve(init.size()); + + for (const auto& v : init) + { + // Ensure elements are unique + ASSERT(map.emplace(v.first, v.second).second); + } + + return map; + } + + static rmap_type make_rmap(init_type init) + { + rmap_type map(init.size()); map.reserve(init.size()); + + for (const auto& v : init) + { + // Ensure elements are unique + ASSERT(map.emplace(v.second, v.first).second); + } + + return map; + } + + static list_type make_list(init_type init) + { + list_type list; list.reserve(init.size()); + + for (const auto& v : init) + { + list.emplace_back(v.second); + } + + return list; + } + + const map_type map; // Direct mapping (enum -> type VT) + const rmap_type rmap; // Reverse mapping (type VT -> enum) + const list_type list; // List of entries (type VT values) sorted in original order + + enum_map(init_type list) + : map(make_map(list)) + , rmap(make_rmap(list)) + , list(make_list(list)) + { + Expects(list.size() != 0); + } + + // Simple lookup by enum value provided (not expected to fail) + VT operator[](T value) const + { + const auto found = map.find(value); + Ensures(found != map.end()); + return found->second; + } +}; + +// Function template forward declaration which returns enum_map<>. +// Specialize it for specific enum type in appropriate cpp file. +// But don't put it in header, don't use inline or static as well. +// Always search in files for working examples. +template +enum_map make_enum_map(); + +// Retrieve global enum_map<> instance for specified types +template +const enum_map& get_enum_map() +{ + // Use magic static + static const enum_map map = make_enum_map(); + return map; +} diff --git a/Utilities/File.cpp b/Utilities/File.cpp index e9cbc3a8afd8..e66a6436ff7c 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -1,5 +1,6 @@ -#include "stdafx.h" #include "File.h" +#include "StrFmt.h" +#include "BEType.h" #ifdef _WIN32 @@ -12,13 +13,13 @@ static std::unique_ptr to_wchar(const std::string& source) { const auto buf_size = source.size() + 1; // size + null terminator - const int size = source.size() < INT_MAX ? static_cast(buf_size) : throw EXCEPTION("Invalid source length (0x%llx)", source.size()); + const int size = source.size() < INT_MAX ? static_cast(buf_size) : throw fmt::exception("to_wchar(): invalid source length (0x%llx)", source.size()); std::unique_ptr buffer(new wchar_t[buf_size]); // allocate buffer assuming that length is the max possible size if (!MultiByteToWideChar(CP_UTF8, 0, source.c_str(), size, buffer.get(), size)) { - throw EXCEPTION("MultiByteToWideChar() failed (0x%x).", GetLastError()); + throw fmt::exception("to_wchar(): MultiByteToWideChar() failed: error %u.", GetLastError()); } return buffer; @@ -28,7 +29,7 @@ static void to_utf8(std::string& result, const wchar_t* source) { const auto length = std::wcslen(source); - const int buf_size = length <= INT_MAX / 3 ? static_cast(length) * 3 + 1 : throw EXCEPTION("Invalid source length (0x%llx)", length); + const int buf_size = length <= INT_MAX / 3 ? static_cast(length) * 3 + 1 : throw fmt::exception("to_utf8(): invalid source length (0x%llx)", length); result.resize(buf_size); // set max possible length for utf-8 + null terminator @@ -38,7 +39,7 @@ static void to_utf8(std::string& result, const wchar_t* source) } else { - throw EXCEPTION("WideCharToMultiByte() failed (0x%x).", GetLastError()); + throw fmt::exception("to_utf8(): WideCharToMultiByte() failed: error %u.", GetLastError()); } } @@ -85,6 +86,57 @@ static time_t to_time(const FILETIME& ft) #endif +namespace fs +{ + static device_manager& get_device_manager() + { + // Use magic static + static device_manager instance; + return instance; + } +} + +safe_buffers std::shared_ptr fs::device_manager::get_device(const std::string& name) +{ + reader_lock lock(m_mutex); + + const auto found = m_map.find(name); + + if (found == m_map.end()) + { + return nullptr; + } + + return found->second; +} + +safe_buffers std::shared_ptr fs::device_manager::set_device(const std::string& name, const std::shared_ptr& device) +{ + std::lock_guard lock(m_mutex); + + return m_map[name] = device; +} + +safe_buffers std::shared_ptr fs::get_virtual_device(const std::string& path) +{ + // Every virtual device path must have "//" at the beginning + if (path.size() > 2 && reinterpret_cast(path.front()) == "//"_u16) + { + return get_device_manager().get_device(path.substr(0, path.find_first_of('/', 2))); + } + + return nullptr; +} + +safe_buffers std::shared_ptr fs::set_virtual_device(const std::string& name, const std::shared_ptr& device) +{ + Expects(name.size() > 2); + Expects(name[0] == '/'); + Expects(name[1] == '/'); + + return get_device_manager().set_device(name, device); +} + std::string fs::get_parent_dir(const std::string& path) { // Search upper bound (set to the last character, npos for empty string) @@ -112,32 +164,37 @@ std::string fs::get_parent_dir(const std::string& path) static const auto test_get_parent_dir = []() -> bool { // Success: - CHECK_ASSERTION(fs::get_parent_dir("/x/y///") == "/x"); - CHECK_ASSERTION(fs::get_parent_dir("/x/y/") == "/x"); - CHECK_ASSERTION(fs::get_parent_dir("/x/y") == "/x"); - CHECK_ASSERTION(fs::get_parent_dir("x:/y") == "x:"); - CHECK_ASSERTION(fs::get_parent_dir("//x/y") == "//x"); + ASSERT(fs::get_parent_dir("/x/y///") == "/x"); + ASSERT(fs::get_parent_dir("/x/y/") == "/x"); + ASSERT(fs::get_parent_dir("/x/y") == "/x"); + ASSERT(fs::get_parent_dir("x:/y") == "x:"); + ASSERT(fs::get_parent_dir("//x/y") == "//x"); // Failure: - CHECK_ASSERTION(fs::get_parent_dir("").empty()); - CHECK_ASSERTION(fs::get_parent_dir("x/").empty()); - CHECK_ASSERTION(fs::get_parent_dir("x").empty()); - CHECK_ASSERTION(fs::get_parent_dir("x///").empty()); - CHECK_ASSERTION(fs::get_parent_dir("/x/").empty()); - CHECK_ASSERTION(fs::get_parent_dir("/x").empty()); - CHECK_ASSERTION(fs::get_parent_dir("/").empty()); - CHECK_ASSERTION(fs::get_parent_dir("//").empty()); - CHECK_ASSERTION(fs::get_parent_dir("//x").empty()); - CHECK_ASSERTION(fs::get_parent_dir("//x/").empty()); - CHECK_ASSERTION(fs::get_parent_dir("///").empty()); - CHECK_ASSERTION(fs::get_parent_dir("///x").empty()); - CHECK_ASSERTION(fs::get_parent_dir("///x/").empty()); + ASSERT(fs::get_parent_dir("").empty()); + ASSERT(fs::get_parent_dir("x/").empty()); + ASSERT(fs::get_parent_dir("x").empty()); + ASSERT(fs::get_parent_dir("x///").empty()); + ASSERT(fs::get_parent_dir("/x/").empty()); + ASSERT(fs::get_parent_dir("/x").empty()); + ASSERT(fs::get_parent_dir("/").empty()); + ASSERT(fs::get_parent_dir("//").empty()); + ASSERT(fs::get_parent_dir("//x").empty()); + ASSERT(fs::get_parent_dir("//x/").empty()); + ASSERT(fs::get_parent_dir("///").empty()); + ASSERT(fs::get_parent_dir("///x").empty()); + ASSERT(fs::get_parent_dir("///x/").empty()); return false; }(); bool fs::stat(const std::string& path, stat_t& info) { + if (auto device = get_virtual_device(path)) + { + return device->stat(path, info); + } + #ifdef _WIN32 WIN32_FILE_ATTRIBUTE_DATA attrs; if (!GetFileAttributesExW(to_wchar(path).get(), GetFileExInfoStandard, &attrs)) @@ -147,7 +204,7 @@ bool fs::stat(const std::string& path, stat_t& info) { case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -179,6 +236,12 @@ bool fs::stat(const std::string& path, stat_t& info) bool fs::exists(const std::string& path) { + if (auto device = get_virtual_device(path)) + { + stat_t info; + return device->stat(path, info); + } + #ifdef _WIN32 if (GetFileAttributesW(to_wchar(path).get()) == INVALID_FILE_ATTRIBUTES) { @@ -187,7 +250,7 @@ bool fs::exists(const std::string& path) { case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -202,6 +265,23 @@ bool fs::exists(const std::string& path) bool fs::is_file(const std::string& path) { + if (auto device = get_virtual_device(path)) + { + stat_t info; + if (!device->stat(path, info)) + { + return false; + } + + if (info.is_directory) + { + errno = EEXIST; + return false; + } + + return true; + } + #ifdef _WIN32 const DWORD attrs = GetFileAttributesW(to_wchar(path).get()); if (attrs == INVALID_FILE_ATTRIBUTES) @@ -211,7 +291,7 @@ bool fs::is_file(const std::string& path) { case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -240,6 +320,23 @@ bool fs::is_file(const std::string& path) bool fs::is_dir(const std::string& path) { + if (auto device = get_virtual_device(path)) + { + stat_t info; + if (!device->stat(path, info)) + { + return false; + } + + if (info.is_directory == false) + { + errno = EEXIST; + return false; + } + + return true; + } + #ifdef _WIN32 const DWORD attrs = GetFileAttributesW(to_wchar(path).get()); if (attrs == INVALID_FILE_ATTRIBUTES) @@ -249,7 +346,7 @@ bool fs::is_dir(const std::string& path) { case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -277,6 +374,11 @@ bool fs::is_dir(const std::string& path) bool fs::create_dir(const std::string& path) { + if (auto device = get_virtual_device(path)) + { + return device->create_dir(path); + } + #ifdef _WIN32 if (!CreateDirectoryW(to_wchar(path).get(), NULL)) { @@ -285,7 +387,7 @@ bool fs::create_dir(const std::string& path) { case ERROR_ALREADY_EXISTS: errno = EEXIST; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -311,6 +413,11 @@ bool fs::create_path(const std::string& path) bool fs::remove_dir(const std::string& path) { + if (auto device = get_virtual_device(path)) + { + return device->remove_dir(path); + } + #ifdef _WIN32 if (!RemoveDirectoryW(to_wchar(path).get())) { @@ -318,7 +425,7 @@ bool fs::remove_dir(const std::string& path) switch (DWORD error = GetLastError()) { case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -332,6 +439,18 @@ bool fs::remove_dir(const std::string& path) bool fs::rename(const std::string& from, const std::string& to) { + const auto device = get_virtual_device(from); + + if (device != get_virtual_device(to)) + { + throw fmt::exception("fs::rename() between different devices not implemented.\nFrom: %s\nTo: %s", from, to); + } + + if (device) + { + return device->rename(from, to); + } + #ifdef _WIN32 if (!MoveFileW(to_wchar(from).get(), to_wchar(to).get())) { @@ -339,7 +458,7 @@ bool fs::rename(const std::string& from, const std::string& to) switch (DWORD error = GetLastError()) { case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.\nFrom: %s\nTo: %s", error, from, to); + default: throw fmt::exception("Unknown Win32 error: %u.\nFrom: %s\nTo: %s" HERE, error, from, to); } return false; @@ -353,6 +472,13 @@ bool fs::rename(const std::string& from, const std::string& to) bool fs::copy_file(const std::string& from, const std::string& to, bool overwrite) { + const auto device = get_virtual_device(from); + + if (device != get_virtual_device(to) || device) // TODO + { + throw fmt::exception("fs::copy_file() for virtual devices not implemented.\nFrom: %s\nTo: %s", from, to); + } + #ifdef _WIN32 if (!CopyFileW(to_wchar(from).get(), to_wchar(to).get(), !overwrite)) { @@ -360,7 +486,7 @@ bool fs::copy_file(const std::string& from, const std::string& to, bool overwrit switch (DWORD error = GetLastError()) { case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.\nFrom: %s\nTo: %s", error, from, to); + default: throw fmt::exception("Unknown Win32 error: %u.\nFrom: %s\nTo: %s" HERE, error, from, to); } return false; @@ -413,6 +539,11 @@ bool fs::copy_file(const std::string& from, const std::string& to, bool overwrit bool fs::remove_file(const std::string& path) { + if (auto device = get_virtual_device(path)) + { + return device->remove(path); + } + #ifdef _WIN32 if (!DeleteFileW(to_wchar(path).get())) { @@ -421,7 +552,7 @@ bool fs::remove_file(const std::string& path) { case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -435,6 +566,11 @@ bool fs::remove_file(const std::string& path) bool fs::truncate_file(const std::string& path, u64 length) { + if (auto device = get_virtual_device(path)) + { + return device->trunc(path, length); + } + #ifdef _WIN32 // Open the file const auto handle = CreateFileW(to_wchar(path).get(), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); @@ -445,7 +581,7 @@ bool fs::truncate_file(const std::string& path, u64 length) { case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -461,7 +597,7 @@ bool fs::truncate_file(const std::string& path, u64 length) switch (DWORD error = GetLastError()) { case ERROR_NEGATIVE_SEEK: errno = EINVAL; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (length=0x%llx).", error, length); + default: throw fmt::exception("Unknown Win32 error: %u (length=0x%llx)." HERE, error, length); } CloseHandle(handle); @@ -475,21 +611,18 @@ bool fs::truncate_file(const std::string& path, u64 length) #endif } -fs::file::~file() +bool fs::file::open(const std::string& path, u32 mode) { - if (m_fd != null) + if (auto device = get_virtual_device(path)) { -#ifdef _WIN32 - CloseHandle((HANDLE)m_fd); -#else - ::close(m_fd); -#endif - } -} + if (auto&& _file = device->open(path, mode)) + { + m_file = std::move(_file); + return true; + } -bool fs::file::open(const std::string& path, u32 mode) -{ - this->close(); + return false; + } #ifdef _WIN32 DWORD access = 0; @@ -517,9 +650,9 @@ bool fs::file::open(const std::string& path, u32 mode) return false; } - m_fd = (std::intptr_t)CreateFileW(to_wchar(path).get(), access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, disp, FILE_ATTRIBUTE_NORMAL, NULL); + const HANDLE handle = CreateFileW(to_wchar(path).get(), access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, disp, FILE_ATTRIBUTE_NORMAL, NULL); - if (m_fd == null) + if (handle == INVALID_HANDLE_VALUE) { // TODO: convert Win32 error code to errno switch (DWORD error = GetLastError()) @@ -527,457 +660,522 @@ bool fs::file::open(const std::string& path, u32 mode) case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; case ERROR_FILE_EXISTS: errno = EEXIST; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; } - return true; -#else - int flags = 0; - - switch (mode & (fom::read | fom::write)) + class windows_file final : public file_base { - case fom::read: flags |= O_RDONLY; break; - case fom::write: flags |= O_WRONLY; break; - case fom::read | fom::write: flags |= O_RDWR; break; - } - - 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; - - m_fd = ::open(path.c_str(), flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - - return m_fd != null; -#endif -} - -bool fs::file::trunc(u64 size) const -{ -#ifdef _WIN32 - LARGE_INTEGER old, pos; + const HANDLE m_handle; - pos.QuadPart = 0; - if (!SetFilePointerEx((HANDLE)m_fd, pos, &old, FILE_CURRENT)) // get old position - { - switch (DWORD error = GetLastError()) + public: + windows_file(HANDLE handle) + : m_handle(handle) { - case ERROR_INVALID_HANDLE: errno = EBADF; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); } - return false; - } - - pos.QuadPart = size; - if (!SetFilePointerEx((HANDLE)m_fd, pos, NULL, FILE_BEGIN)) // set new position - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) + ~windows_file() override { - case ERROR_NEGATIVE_SEEK: errno = EINVAL; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); + CloseHandle(m_handle); } - return false; - } - - const BOOL result = SetEndOfFile((HANDLE)m_fd); // change file size + stat_t stat() override + { + FILE_BASIC_INFO basic_info; + if (!GetFileInformationByHandleEx(m_handle, FileBasicInfo, &basic_info, sizeof(FILE_BASIC_INFO))) + { + // TODO: convert Win32 error code to errno + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Win32 error: %u." HERE, error); + } + } + + stat_t info; + 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); + + return info; + } - if (!result) - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) + bool trunc(u64 length) override { - case 0: - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); + LARGE_INTEGER old, pos; + + pos.QuadPart = 0; + if (!SetFilePointerEx(m_handle, pos, &old, FILE_CURRENT)) // get old position + { + // TODO: convert Win32 error code to errno + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Unknown Win32 error: %u." HERE, error); + } + + return false; + } + + pos.QuadPart = length; + if (!SetFilePointerEx(m_handle, pos, NULL, FILE_BEGIN)) // set new position + { + // TODO: convert Win32 error code to errno + switch (DWORD error = GetLastError()) + { + case ERROR_NEGATIVE_SEEK: errno = EINVAL; break; + default: throw fmt::exception("Unknown Win32 error: %u." HERE, error); + } + + return false; + } + + const BOOL result = SetEndOfFile(m_handle); // change file size + + if (!result) + { + // TODO: convert Win32 error code to errno + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Unknown Win32 error: %u." HERE, error); + } + } + + if (!SetFilePointerEx(m_handle, old, NULL, FILE_BEGIN) && result) // restore position + { + if (DWORD error = GetLastError()) + { + throw fmt::exception("Win32 error: %u." HERE, error); + } + } + + return result != FALSE; } - } - if (!SetFilePointerEx((HANDLE)m_fd, old, NULL, FILE_BEGIN) && result) // restore position - { - if (DWORD error = GetLastError()) + u64 read(void* buffer, u64 count) override { - throw EXCEPTION("Unknown Win32 error: 0x%x.", error); + // TODO (call ReadFile multiple times if count is too big) + const int size = ::narrow(count, "Too big count" HERE); + Expects(size >= 0); + + DWORD nread; + if (!ReadFile(m_handle, buffer, size, &nread, NULL)) + { + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Win32 error: %u." HERE, error); + } + } + + return nread; } - } - return result != FALSE; -#else - return !::ftruncate(m_fd, size); -#endif -} + u64 write(const void* buffer, u64 count) override + { + // TODO (call WriteFile multiple times if count is too big) + const int size = ::narrow(count, "Too big count" HERE); + Expects(size >= 0); + + DWORD nwritten; + if (!WriteFile(m_handle, buffer, size, &nwritten, NULL)) + { + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Win32 error: %u." HERE, error); + } + } + + return nwritten; + } -bool fs::file::stat(stat_t& info) const -{ -#ifdef _WIN32 - FILE_BASIC_INFO basic_info; - if (!GetFileInformationByHandleEx((HANDLE)m_fd, FileBasicInfo, &basic_info, sizeof(FILE_BASIC_INFO))) - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) + u64 seek(s64 offset, seek_mode whence) override { - case ERROR_INVALID_HANDLE: errno = EBADF; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); + LARGE_INTEGER pos; + pos.QuadPart = offset; + + const DWORD mode = + whence == seek_set ? FILE_BEGIN : + whence == seek_cur ? FILE_CURRENT : + whence == seek_end ? FILE_END : + throw fmt::exception("Invalid whence (0x%x)" HERE, whence); + + if (!SetFilePointerEx(m_handle, pos, &pos, mode)) + { + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Win32 error: %u." HERE, error); + } + } + + return pos.QuadPart; } - return false; - } + u64 size() override + { + LARGE_INTEGER size; + if (!GetFileSizeEx(m_handle, &size)) + { + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Win32 error: %u." HERE, error); + } + } + + return size.QuadPart; + } + }; - 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); + m_file = std::make_unique(handle); #else - struct ::stat file_info; - if (::fstat(m_fd, &file_info) < 0) + int flags = 0; + + switch (mode & (fom::read | fom::write)) { - return false; + case fom::read: flags |= O_RDONLY; break; + case fom::write: flags |= O_WRONLY; break; + case fom::read | fom::write: flags |= O_RDWR; break; } - 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 + 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; - return true; -} + const int fd = ::open(path.c_str(), flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); -void fs::file::close() -{ - if (m_fd == null) + if (fd == -1) { - return /*true*/; + // TODO: errno + return false; } - const auto fd = m_fd; - m_fd = null; - -#ifdef _WIN32 - if (!CloseHandle((HANDLE)fd)) - { - throw EXCEPTION("CloseHandle() failed (fd=0x%llx, 0x%x)", fd, GetLastError()); - } -#else - if (::close(fd) != 0) + class unix_file final : public file_base { - throw EXCEPTION("close() failed (fd=0x%llx, errno=%d)", fd, errno); - } -#endif -} - -u64 fs::file::read(void* buffer, u64 count) const -{ - // 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); + const int m_fd; -#ifdef _WIN32 - DWORD nread; - if (!ReadFile((HANDLE)m_fd, buffer, size, &nread, NULL)) - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) + public: + unix_file(int fd) + : m_fd(fd) { - case ERROR_INVALID_HANDLE: errno = EBADF; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); } - return -1; - } - - return nread; -#else - return ::read(m_fd, buffer, size); -#endif -} - -u64 fs::file::write(const void* buffer, u64 count) const -{ - // 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); + ~unix_file() override + { + ::close(m_fd); + } -#ifdef _WIN32 - DWORD nwritten; - if (!WriteFile((HANDLE)m_fd, buffer, size, &nwritten, NULL)) - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) + stat_t stat() override { - case ERROR_INVALID_HANDLE: errno = EBADF; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); + struct ::stat file_info; + if (::fstat(m_fd, &file_info) != 0) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } + } + + stat_t info; + 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; + + return info; } - return -1; - } + bool trunc(u64 length) override + { + if (::ftruncate(m_fd, length) != 0) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } - return nwritten; -#else - return ::write(m_fd, buffer, size); -#endif -} + return false; + } -u64 fs::file::seek(s64 offset, seek_mode whence) const -{ -#ifdef _WIN32 - LARGE_INTEGER pos; - pos.QuadPart = offset; + return true; + } - DWORD mode; - switch (whence) - { - case seek_set: mode = FILE_BEGIN; break; - case seek_cur: mode = FILE_CURRENT; break; - case seek_end: mode = FILE_END; break; - default: - { - errno = EINVAL; - return -1; - } - } + u64 read(void* buffer, u64 count) override + { + const auto result = ::read(m_fd, buffer, count); + if (result == -1) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } + } + + return result; + } - if (!SetFilePointerEx((HANDLE)m_fd, pos, &pos, mode)) - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) + u64 write(const void* buffer, u64 count) override { - case ERROR_INVALID_HANDLE: errno = EBADF; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); + const auto result = ::write(m_fd, buffer, count); + if (result == -1) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } + } + + return result; } - return -1; - } + u64 seek(s64 offset, seek_mode whence) override + { + const int mode = + whence == seek_set ? SEEK_SET : + whence == seek_cur ? SEEK_CUR : + whence == seek_end ? SEEK_END : + throw fmt::exception("Invalid whence (0x%x)" HERE, whence); + + const auto result = ::lseek(m_fd, offset, mode); + if (result == -1) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } + } + + return result; + } - return pos.QuadPart; -#else - int mode; - switch (whence) - { - case seek_set: mode = SEEK_SET; break; - case seek_cur: mode = SEEK_CUR; break; - case seek_end: mode = SEEK_END; break; - default: - { - errno = EINVAL; - return -1; - } - } + u64 size() override + { + struct ::stat file_info; + if (::fstat(m_fd, &file_info) != 0) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } + } + + return file_info.st_size; + } + }; - return ::lseek(m_fd, offset, mode); + m_file = std::make_unique(fd); #endif + + return true; } -u64 fs::file::size() const +//void fs::file_read_map::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_read_map::reset() +//{ +// if (m_ptr) +// { +//#ifdef _WIN32 +// ::UnmapViewOfFile(m_ptr); +//#else +// ::munmap(m_ptr, m_size); +//#endif +// } +//} + +bool fs::dir::open(const std::string& path) { -#ifdef _WIN32 - LARGE_INTEGER size; - if (!GetFileSizeEx((HANDLE)m_fd, &size)) + if (auto device = get_virtual_device(path)) { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) + if (auto&& _dir = device->open_dir(path)) { - case ERROR_INVALID_HANDLE: errno = EBADF; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); + m_dir = std::move(_dir); + return true; } - return -1; - } - - return size.QuadPart; -#else - struct ::stat file_info; - if (::fstat(m_fd, &file_info) != 0) - { - return -1; + return false; } - 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_read_map::reset(const file& f) -{ - reset(); + WIN32_FIND_DATAW found; + const auto handle = FindFirstFileW(to_wchar(path + "/*").get(), &found); - if (f) + if (handle == INVALID_HANDLE_VALUE) { -#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 + // TODO: convert Win32 error code to errno + switch (DWORD error = GetLastError()) + { + case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; + case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; + default: throw fmt::exception("Unknown Win32 error: %u." HERE, error); + } + + return false; } -} -void fs::file_read_map::reset() -{ - if (m_ptr) + class windows_dir final : public dir_base { -#ifdef _WIN32 - ::UnmapViewOfFile(m_ptr); -#else - ::munmap(m_ptr, m_size); -#endif - } -} + const HANDLE m_handle; -bool fs::dir::open(const std::string& dirname) -{ - this->close(); + std::vector m_entries; + std::size_t m_pos = 0; -#ifdef _WIN32 - if (!is_dir(dirname)) - { - return false; - } + void add_entry(const WIN32_FIND_DATAW& found) + { + dir_entry info; - m_dd = -1; -#else - const auto ptr = ::opendir(m_path.get()); - if (!ptr) - { - return false; - } + to_utf8(info.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); - m_dd = reinterpret_cast(ptr); -#endif + m_entries.emplace_back(std::move(info)); + } - m_path.reset(new char[dirname.size() + 1]); - std::memcpy(m_path.get(), dirname.c_str(), dirname.size() + 1); + public: + windows_dir(HANDLE handle, const WIN32_FIND_DATAW& found) + : m_handle(handle) + { + add_entry(found); + } - return true; -} + ~windows_dir() + { + FindClose(m_handle); + } -void fs::dir::close() -{ - if (!m_path) - { - return /*true*/; - } + bool read(dir_entry& out) override + { + if (m_pos == m_entries.size()) + { + WIN32_FIND_DATAW found; + if (!FindNextFileW(m_handle, &found)) + { + switch (DWORD error = GetLastError()) + { + case ERROR_NO_MORE_FILES: return false; + default: throw fmt::exception("Unknown Win32 error: %u." HERE, error); + } + } + + add_entry(found); + } + + out = m_entries[m_pos++]; + return true; + } - m_path.reset(); + void rewind() override + { + m_pos = 0; + } + }; -#ifdef _WIN32 - CHECK_ASSERTION(m_dd == -1 || FindClose((HANDLE)m_dd)); + m_dir = std::make_unique(handle, found); #else - CHECK_ASSERTION(!::closedir((DIR*)m_dd)); -#endif -} + ::DIR* const ptr = ::opendir(path.c_str()); -bool fs::dir::read(std::string& name, stat_t& info) -{ - if (!m_path) + if (!ptr) { - errno = EINVAL; + // TODO: errno return false; } -#ifdef _WIN32 - const bool is_first = m_dd == -1; - - WIN32_FIND_DATAW found; - - if (is_first) + class unix_dir final : public dir_base { - m_dd = (std::intptr_t)FindFirstFileW(to_wchar(m_path.get() + "/*"s).get(), &found); - } + ::DIR* m_dd; - if (is_first && m_dd == -1 || !is_first && !FindNextFileW((HANDLE)m_dd, &found)) - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) - { - case ERROR_NO_MORE_FILES: + public: + unix_dir(::DIR* dd) + : m_dd(dd) { - name.clear(); - return true; } - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); + ~unix_dir() override + { + ::closedir(m_dd); } - 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); - if (!found) - { - name.clear(); - return true; - } + bool read(dir_entry& info) override + { + const auto found = ::readdir(m_dd); + if (!found) + { + return false; + } + + struct ::stat file_info; + if (::fstatat(::dirfd(m_dd), found->d_name, &file_info, 0) != 0) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } + } + + info.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; - struct ::stat file_info; - if (::fstatat(::dirfd((DIR*)m_dd), found->d_name, &file_info, 0) != 0) - { - return false; - } + return true; + } - name = found->d_name; + void rewind() override + { + ::rewinddir(m_dd); + } + }; - 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; + m_dir = std::make_unique(ptr); #endif return true; } -bool fs::dir::first(std::string& name, stat_t& info) -{ -#ifdef _WIN32 - if (m_path && m_dd != -1) - { - CHECK_ASSERTION(FindClose((HANDLE)m_dd)); - m_dd = -1; - } -#else - if (m_path) - { - ::rewinddir((DIR*)m_dd); - } -#endif - - return read(name, info); -} - const std::string& fs::get_config_dir() { - // Use magic static for dir initialization + // Use magic static static const std::string s_dir = [] { #ifdef _WIN32 @@ -1009,7 +1207,7 @@ const std::string& fs::get_config_dir() const std::string& fs::get_executable_dir() { - // Use magic static for dir initialization + // Use magic static static const std::string s_dir = [] { std::string dir; @@ -1018,7 +1216,7 @@ const std::string& fs::get_executable_dir() 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); + MessageBoxA(0, fmt::format("GetModuleFileName() failed: error %u.", GetLastError()).c_str(), "fs::get_config_dir()", MB_ICONERROR); return dir; // empty } @@ -1055,3 +1253,51 @@ const std::string& fs::get_executable_dir() return s_dir; } + +void fs::remove_all(const std::string& path) +{ + for (const auto& entry : dir(path)) + { + if (entry.name == "." || entry.name == "..") + { + continue; + } + + if (entry.is_directory == false) + { + remove_file(path + '/' + entry.name); + } + + if (entry.is_directory == true) + { + remove_all(path + '/' + entry.name); + } + } + + remove_dir(path); +} + +u64 fs::get_dir_size(const std::string& path) +{ + u64 result = 0; + + for (const auto entry : dir(path)) + { + if (entry.name == "." || entry.name == "..") + { + continue; + } + + if (entry.is_directory == false) + { + result += entry.size; + } + + if (entry.is_directory == true) + { + result += get_dir_size(path + '/' + entry.name); + } + } + + return result; +} diff --git a/Utilities/File.h b/Utilities/File.h index 36af62704f2f..aca266ff99a2 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -1,5 +1,13 @@ #pragma once +#include +#include +#include +#include + +#include "types.h" +#include "SharedMutex.h" + namespace fom // file open mode { enum open_mode : u32 @@ -17,13 +25,15 @@ namespace fom // file open mode namespace fs { - enum seek_mode : u32 // file seek mode + // File seek mode + enum seek_mode : u32 { seek_set, seek_cur, seek_end, }; + // File attributes (TODO) struct stat_t { bool is_directory; @@ -34,6 +44,67 @@ namespace fs s64 ctime; }; + // File handle base + struct file_base + { + virtual ~file_base() = default; + + virtual stat_t stat() = 0; + virtual bool trunc(u64 length) = 0; + virtual u64 read(void* buffer, u64 size) = 0; + virtual u64 write(const void* buffer, u64 size) = 0; + virtual u64 seek(s64 offset, seek_mode whence) = 0; + virtual u64 size() = 0; + }; + + // Directory entry (TODO) + struct dir_entry : stat_t + { + std::string name; + }; + + // Directory handle base + struct dir_base + { + virtual ~dir_base() = default; + + virtual bool read(dir_entry&) = 0; + virtual void rewind() = 0; + }; + + // Virtual device + struct device_base + { + virtual ~device_base() = default; + + virtual bool stat(const std::string& path, stat_t& info) = 0; + virtual bool remove_dir(const std::string& path) = 0; + virtual bool create_dir(const std::string& path) = 0; + virtual bool rename(const std::string& from, const std::string& to) = 0; + virtual bool remove(const std::string& path) = 0; + virtual bool trunc(const std::string& path, u64 length) = 0; + + virtual std::unique_ptr open(const std::string& path, u32 mode) = 0; + virtual std::unique_ptr open_dir(const std::string& path) = 0; + }; + + class device_manager final + { + mutable shared_mutex m_mutex; + + std::unordered_map> m_map; + + public: + std::shared_ptr get_device(const std::string& name); + std::shared_ptr set_device(const std::string& name, const std::shared_ptr&); + }; + + // Get virtual device for specified path (nullptr for real path) + std::shared_ptr get_virtual_device(const std::string& path); + + // Set virtual device with specified name (nullptr for deletion) + std::shared_ptr set_virtual_device(const std::string& root_name, const std::shared_ptr&); + // Get parent directory for the path (returns empty string on failure) std::string get_parent_dir(const std::string& path); @@ -72,77 +143,95 @@ namespace fs class file final { - using handle_type = std::intptr_t; - - constexpr static handle_type null = -1; - - handle_type m_fd = null; - - friend class file_read_map; - friend class file_write_map; + std::unique_ptr m_file; public: file() = default; + // Open file handler explicit file(const std::string& path, u32 mode = fom::read) { open(path, mode); } - file(file&& other) - : m_fd(other.m_fd) - { - other.m_fd = null; - } - - file& operator =(file&& right) + // Import file handler + template::value>> + file(std::unique_ptr&& _file) + : m_file(std::move(_file)) { - 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; - } + // Forbid nullptr arg + file(std::nullptr_t) = delete; // Check whether the handle is valid (opened file) explicit operator bool() const { - return is_opened(); + return m_file.operator bool(); } // Open specified file with specified mode bool open(const std::string& path, u32 mode = fom::read); + // Close the file explicitly, return file handler + std::unique_ptr release() + { + return std::move(m_file); + } + // Change file size (possibly appending zero bytes) - bool trunc(u64 size) const; + bool trunc(u64 length) const + { + Expects(m_file); + return m_file->trunc(length); + } // Get file information - bool stat(stat_t& info) const; - - // Close the file explicitly (destructor automatically closes the file) - void close(); + stat_t stat() const + { + Expects(m_file); + return m_file->stat(); + } // Read the data from the file and return the amount of data written in buffer - u64 read(void* buffer, u64 count) const; + u64 read(void* buffer, u64 count) const + { + Expects(m_file); + return m_file->read(buffer, count); + } // Write the data to the file and return the amount of data actually written - u64 write(const void* buffer, u64 count) const; + u64 write(const void* buffer, u64 count) const + { + Expects(m_file); + return m_file->write(buffer, count); + } - // Move file pointer - u64 seek(s64 offset, seek_mode whence = seek_set) const; + // Change current position, returns previous position + u64 seek(s64 offset, seek_mode whence = seek_set) const + { + Expects(m_file); + return m_file->seek(offset, whence); + } // Get file size - u64 size() const; + u64 size() const + { + Expects(m_file); + return m_file->size(); + } + + // Get current position + u64 pos() const + { + Expects(m_file); + return m_file->seek(0, seek_cur); + } // Write std::string unconditionally const file& write(const std::string& str) const { - CHECK_ASSERTION(write(str.data(), str.size()) == str.size()); + ASSERT(write(str.data(), str.size()) == str.size()); return *this; } @@ -150,7 +239,7 @@ namespace fs template std::enable_if_t::value && !std::is_pointer::value, const file&> write(const T& data) const { - CHECK_ASSERTION(write(std::addressof(data), sizeof(T)) == sizeof(T)); + ASSERT(write(std::addressof(data), sizeof(T)) == sizeof(T)); return *this; } @@ -158,7 +247,7 @@ namespace fs template std::enable_if_t::value && !std::is_pointer::value, const file&> write(const std::vector& vec) const { - CHECK_ASSERTION(write(vec.data(), vec.size() * sizeof(T)) == vec.size() * sizeof(T)); + ASSERT(write(vec.data(), vec.size() * sizeof(T)) == vec.size() * sizeof(T)); return *this; } @@ -187,7 +276,7 @@ namespace fs std::enable_if_t::value && !std::is_pointer::value, T> read() const { T result; - CHECK_ASSERTION(read(result)); + ASSERT(read(result)); return result; } @@ -196,7 +285,7 @@ namespace fs { std::string result; result.resize(size()); - CHECK_ASSERTION(seek(0) != -1 && read(result)); + ASSERT(seek(0) == 0 && read(result)); return result; } @@ -206,164 +295,67 @@ namespace fs { std::vector result; result.resize(size() / sizeof(T)); - CHECK_ASSERTION(seek(0) != -1 && read(result)); + ASSERT(seek(0) == 0 && read(result)); return result; } }; - // TODO - class file_read_map final - { - char* m_ptr = nullptr; - u64 m_size; - - public: - file_read_map() = default; - - file_read_map(file_read_map&& right) - : m_ptr(right.m_ptr) - , m_size(right.m_size) - { - right.m_ptr = 0; - } - - file_read_map& operator =(file_read_map&& right) - { - std::swap(m_ptr, right.m_ptr); - std::swap(m_size, right.m_size); - return *this; - } - - file_read_map(const file& f) - { - reset(f); - } - - ~file_read_map() - { - reset(); - } - - // Open file mapping - void reset(const file& f); - - // Close file mapping - void reset(); - - // Get pointer - operator const char*() const - { - return m_ptr; - } - }; - - // TODO - class file_write_map final - { - char* m_ptr = nullptr; - u64 m_size; - - public: - file_write_map() = default; - - file_write_map(file_write_map&& right) - : m_ptr(right.m_ptr) - , m_size(right.m_size) - { - right.m_ptr = 0; - } - - file_write_map& operator =(file_write_map&& right) - { - std::swap(m_ptr, right.m_ptr); - std::swap(m_size, right.m_size); - return *this; - } - - file_write_map(const file& f) - { - reset(f); - } - - ~file_write_map() - { - 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) + std::unique_ptr m_dir; public: dir() = default; - explicit dir(const std::string& dirname) + // Open dir handle + explicit dir(const std::string& path) { - open(dirname); + open(path); } - dir(dir&& other) - : m_dd(other.m_dd) - , m_path(std::move(other.m_path)) + // Import dir handler + template::value>> + dir(std::unique_ptr&& _dir) + : m_dir(std::move(_dir)) { } - 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(); - } + // Forbid nullptr arg + dir(std::nullptr_t) = delete; // Check whether the handle is valid (opened directory) explicit operator bool() const { - return is_opened(); + return m_dir.operator bool(); } // Open specified directory - bool open(const std::string& dirname); + bool open(const std::string& path); - // Close the directory explicitly (destructor automatically closes the directory) - void close(); - - // Get next directory entry (UTF-8 name and file stat) - bool read(std::string& name, stat_t& info); + // Close the directory explicitly, return dir_base + std::unique_ptr release() + { + return std::move(m_dir); + } - bool first(std::string& name, stat_t& info); + // Get next directory entry + bool read(dir_entry& out) + { + Expects(m_dir); + return m_dir->read(out); + } - struct entry + // Reset to the beginning + void rewind() { - std::string name; - stat_t info; - }; + Expects(m_dir); + return m_dir->rewind(); + } class iterator { - entry m_entry; dir* m_parent; + dir_entry m_entry; public: enum class mode @@ -382,20 +374,16 @@ namespace fs if (mode_ == mode::from_first) { - m_parent->first(m_entry.name, m_entry.info); - } - else - { - m_parent->read(m_entry.name, m_entry.info); + m_parent->rewind(); } - if (m_entry.name.empty()) + if (!m_parent->read(m_entry)) { m_parent = nullptr; } } - entry& operator *() + dir_entry& operator *() { return m_entry; } @@ -414,7 +402,7 @@ namespace fs iterator begin() { - return{ this }; + return{ m_dir ? this : nullptr }; } iterator end() @@ -428,4 +416,69 @@ namespace fs // Get executable directory const std::string& get_executable_dir(); + + // Delete directory and all its contents recursively + void remove_all(const std::string& path); + + // Get size of all files recursively + u64 get_dir_size(const std::string& path); } + +// For compatibility (it's not a good idea) +class memory_stream final : public fs::file_base +{ + u64 m_pos; + u64 m_size; + +public: + const std::add_pointer_t ptr; + + [[deprecated]] memory_stream(void* ptr, u64 max_size) + : m_pos(0) + , m_size(max_size) + , ptr(static_cast(ptr)) + { + } + + fs::stat_t stat() override + { + throw std::logic_error("memory_stream doesn't support stat()"); + } + + bool trunc(u64 length) override + { + throw std::logic_error("memory_stream doesn't support trunc()"); + } + + u64 read(void* buffer, u64 count) override + { + const u64 start = m_pos; + const u64 end = seek(count, fs::seek_cur); + const u64 read_size = end >= start ? end - start : throw std::logic_error("memory_stream::read(): overflow"); + std::memmove(buffer, ptr + start, read_size); + return read_size; + } + + u64 write(const void* buffer, u64 count) override + { + const u64 start = m_pos; + const u64 end = seek(count, fs::seek_cur); + const u64 write_size = end >= start ? end - start : throw std::logic_error("memory_stream::write(): overflow"); + std::memmove(ptr + start, buffer, write_size); + return write_size; + } + + u64 seek(s64 offset, fs::seek_mode whence) override + { + return m_pos = + whence == fs::seek_set ? std::min(offset, m_size) : + whence == fs::seek_cur ? std::min(offset + m_pos, m_size) : + whence == fs::seek_end ? std::min(offset + m_size, m_size) : + throw std::logic_error("memory_stream::seek(): invalid whence"); + } + + u64 size() override + { + return m_size; + } +}; diff --git a/Utilities/Log.cpp b/Utilities/Log.cpp index d86e235c62f2..6f7a5ee9d359 100644 --- a/Utilities/Log.cpp +++ b/Utilities/Log.cpp @@ -1,4 +1,5 @@ #include "stdafx.h" +#include "Registry.h" #include "Thread.h" #include "File.h" #include "Log.h" @@ -9,9 +10,9 @@ namespace _log { - logger& get_logger() + static logger& get_logger() { - // Use magic static for global logger instance + // Use magic static static logger instance; return instance; } @@ -30,6 +31,22 @@ namespace _log channel ARMv7("ARMv7"); } +template<> +enum_map<_log::level> make_enum_map() +{ + return + { + { _log::level::always, "Nothing" }, + { _log::level::fatal, "Fatal" }, + { _log::level::error, "Error" }, + { _log::level::todo, "TODO" }, + { _log::level::success, "Success" }, + { _log::level::warning, "Warning" }, + { _log::level::notice, "Notice" }, + { _log::level::trace, "Trace" }, + }; +} + _log::listener::listener() { // Register self @@ -46,7 +63,68 @@ _log::channel::channel(const std::string& name, _log::level init_level) : name{ name } , enabled{ init_level } { - // TODO: register config property "name" associated with "enabled" member + struct property : cfg::property_base + { + std::atomic& value; + const level def; + const enum_map& emap = get_enum_map(); + + property(std::atomic& value, level def) + : value(value) + , def(def) + { + } + + cfg::type get_type() const override + { + return cfg::type::enumeration; + } + + std::vector get_values() const override + { + return emap.list; + } + + std::string to_string() const override + { + return emap[value]; + } + + bool validate(const std::string& name) const override + { + return emap.rmap.count(name) != 0; + } + + bool from_string(const std::string& name) override + { + const auto found = emap.rmap.find(name); + + if (found != emap.rmap.end()) + { + value = found->second; + return true; + } + + return false; + } + + void from_default() override + { + value = def; + } + + bool is_default() const override + { + return value == def; + } + }; + + cfg::register_property("log/" + name, std::make_unique(enabled, init_level)); +} + +_log::channel::~channel() +{ + cfg::unregister_property("log/" + name); } void _log::logger::add_listener(_log::listener* listener) @@ -87,7 +165,7 @@ _log::file_writer::file_writer(const std::string& name) throw EXCEPTION("Can't create log file %s (error %d)", name, errno); } } - catch (const fmt::exception& e) + catch (const std::exception& e) { #ifdef _WIN32 MessageBoxA(0, e.what(), "_log::file_writer() failed", MB_ICONERROR); @@ -104,7 +182,7 @@ void _log::file_writer::log(const std::string& text) std::size_t _log::file_writer::size() const { - return m_file.seek(0, fs::seek_cur); + return m_file.pos(); } void _log::file_listener::log(const _log::channel& ch, _log::level sev, const std::string& text) diff --git a/Utilities/Log.h b/Utilities/Log.h index 11ef6c6c21a2..da4873373474 100644 --- a/Utilities/Log.h +++ b/Utilities/Log.h @@ -52,7 +52,7 @@ namespace _log // Initialization (max level enabled by default) channel(const std::string& name, level = level::trace); - virtual ~channel() = default; + virtual ~channel(); // Log without formatting force_inline void log(level sev, const std::string& text) const @@ -71,7 +71,7 @@ namespace _log #define GEN_LOG_METHOD(_sev)\ template\ - force_inline void _sev(const char* fmt, const Args&... args)\ + force_inline void _sev(const char* fmt, const Args&... args) const\ {\ return format(level::_sev, fmt, args...);\ } diff --git a/Utilities/Macro.h b/Utilities/Macro.h new file mode 100644 index 000000000000..1247219b0070 --- /dev/null +++ b/Utilities/Macro.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#include "Const.h" + +#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") +#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)) + +// Return 32 bit sizeof() to avoid widening/narrowing conversions with size_t +#define SIZE_32(type) static_cast(sizeof(type)) + +// Return 32 bit alignof() to avoid widening/narrowing conversions with size_t +#define ALIGN_32(type) static_cast(alignof(type)) + +// Return 32 bit offsetof() +#define OFFSET_32(type, x) static_cast(reinterpret_cast(&reinterpret_cast(reinterpret_cast(0ull)->x))) + +#define CONCATENATE_DETAIL(x, y) x ## y +#define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y) + +#define STRINGIZE_DETAIL(x) #x +#define STRINGIZE(x) STRINGIZE_DETAIL(x) + +#define SWITCH(expr) for (const auto& CONCATENATE(_switch_, __LINE__) = (expr);;) { const auto& _switch_ = CONCATENATE(_switch_, __LINE__); +#define CCASE(value) } constexpr std::remove_reference_t CONCATENATE(_value_, __LINE__) = value; if (_switch_ == CONCATENATE(_value_, __LINE__)) { +#define VCASE(value) } if (_switch_ == (value)) { +#define DEFAULT } /* must be the last one */ + +/* Macro set, intended to hide "return" in lambda expressions. */ + +#define WRAP_EXPR(expr, ...) [&](__VA_ARGS__) { return expr; } +#define COPY_EXPR(expr, ...) [=](__VA_ARGS__) { return expr; } +#define PURE_EXPR(expr, ...) [] (__VA_ARGS__) { return expr; } + +#define HERE "\n(in file " __FILE__ ":" STRINGIZE(__LINE__) ")" + +// Ensure that the expression is evaluated to true. Always evaluated and allowed to have side effects (unlike assert() macro). +#define ASSERT(expr) if (!(expr)) throw std::runtime_error("Assertion failed: " #expr HERE) + +#ifdef Expects +#undef Expects +#endif + +#ifdef Ensures +#undef Ensures +#endif + +// Expects() and Ensures() are intended to check function arguments and results. +// Expressions are not guaranteed to evaluate. Redefinition with ASSERT macro for better unification. +#define Expects ASSERT +#define Ensures ASSERT diff --git a/Utilities/GNU.h b/Utilities/Platform.h similarity index 99% rename from Utilities/GNU.h rename to Utilities/Platform.h index 6bf8c7c8866c..943858775a89 100644 --- a/Utilities/GNU.h +++ b/Utilities/Platform.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #if defined(_MSC_VER) && _MSC_VER <= 1800 #define thread_local __declspec(thread) @@ -33,7 +35,6 @@ #if defined(__GNUG__) #include -#include #define _fpclass(x) std::fpclassify(x) #define INFINITE 0xFFFFFFFF diff --git a/Utilities/Registry.cpp b/Utilities/Registry.cpp new file mode 100644 index 000000000000..ddf8cb2f1501 --- /dev/null +++ b/Utilities/Registry.cpp @@ -0,0 +1,424 @@ +#include "stdafx.h" +#include "Thread.h" +#include "Registry.h" + +namespace cfg +{ + const extern _log::channel log("CFG"); + + static registry& get_registry() + { + try + { + // Use magic static + static registry config(fs::get_config_dir() + "config.ini"); + return config; + } + catch (...) + { + catch_all_exceptions(); + } + } + + // Property placeholder, contains string value + struct raw_property final : property_base + { + std::string value; + + raw_property(const std::string& value) + : value(value) + { + } + + type get_type() const override + { + return type::unknown; + } + + std::string to_string() const override + { + return value; + } + + bool validate(const std::string& value) const override + { + return true; + } + + bool from_string(const std::string& value) override + { + this->value = value; + return true; + } + + void from_default() override + { + // Do nothing (default value is unknown) + } + + bool is_default() const override + { + return false; // TODO: is it correct? + } + }; +} + +cfg::registry::registry(const std::string& path) + : m_main_file(path, fom::read | fom::write | fom::create) +{ + if (m_main_file) + { + // Load config + for (auto&& v : deserialize(m_main_file.to_string())) + { + // Initialize raw property handlers + m_map.emplace(v.first, std::make_unique(std::move(v.second))); + } + } +} + +void cfg::registry::save() +{ + std::lock_guard lock(m_mutex); + + if (m_main_file && m_layers.empty()) + { + map_type props; + + // Store config + for (const auto& v : m_map) + { + // Store only non-default values + if (!v.second->is_default()) + props.emplace(v.first, v.second->to_string()); + } + + m_main_file.seek(0); + m_main_file.trunc(0); + m_main_file.write(serialize(props)); + } +} + +cfg::registry::map_type cfg::registry::deserialize(const std::string& text) +{ + cfg::registry::map_type result; + + std::string last_group_name; + + const auto text_end = text.end(); // With this, MSVC generates smaller code, lol. + + auto is_delimiter = PURE_EXPR(c >= 0 && c <= 31, char c); + + for (auto it = text.begin(); it != text_end;) + { + // Get string range, update `it` + const auto start = std::find_if_not(it, text_end, is_delimiter); + const auto end = it = std::find_if(start, text_end, is_delimiter); + + // Detect group + if (start != end && *start == '[' && *(end - 1) == ']') + { + auto&& group = fmt::unescape({ start + 1, end - 1 }); + + // Validate group name + if (group.empty() || group.back() == '/') + { + last_group_name = std::move(group); + } + } + else + { + // Find name delimiter + const auto delim = std::find(start, end, '='); + + // Validate property name (name found and doesn't contain /) + if (delim != end && std::find(start, delim, '/') == delim) + { + auto&& name = fmt::unescape({ start, delim }); + auto&& value = fmt::unescape({ delim + 1, end }); + + // Add property + result.emplace(last_group_name + name, std::move(value)); + } + } + } + + return result; +} + +std::string cfg::registry::serialize(const cfg::registry::map_type& map) +{ + std::map> groups; + + // Sort properties in groups + for (const auto& p : map) + { + const auto& p_name = p.first.substr(p.first.find_last_of('/') + 1); + const auto& g_name = p.first.substr(0, p.first.size() - p_name.size()); + + groups[g_name][p_name] = p.second; + } + + std::string result; + + // Append to string + for (const auto& g : groups) + { + // Write escaped group name + result += '['; + result += fmt::escape(g.first); + result += "]\n"; + + for (const auto& p : g.second) + { + // Escape also '=' and '[' characters in property name + result += fmt::escape(p.first, { '=', '[' }); + result += '='; + result += fmt::escape(p.second); + result += '\n'; + } + + result += '\n'; + } + + return result; +} + +void cfg::registry::set_property_handler(const std::string& name, std::unique_ptr data) +{ + std::lock_guard lock(m_mutex); + + const auto found = m_map.find(name); + + if (found != m_map.end()) + { + // Preserve previous value + const std::string& value = found->second->to_string(); + const type old_type = found->second->get_type(); + + if (old_type != type::unknown) + { + log.error("add_property('%s'): already registered with type %d", name, old_type); + } + + log.trace("add_property('%s'): old value %s", name, value); + found->second.reset(); + + // Try to restore value + if (!data->from_string(value)) + { + log.warning("add_property('%s'): failed to restore value %s", name, value); + } + + // Install new property handler + log.trace("add_property('%s'): property replaced with value %s", name, data->to_string()); + found->second = std::move(data); + } + else + { + log.trace("add_property('%s'): new property added with value %s", name, data->to_string()); + m_map.emplace(name, std::move(data)); + } +} + +void cfg::registry::remove_property_handler(const std::string& name) +{ + std::lock_guard lock(m_mutex); + + const auto found = m_map.find(name); + + // Compare registered property with a pointer provided (if not null) + if (found != m_map.end()) + { + // Preserve previous value + const std::string& value = found->second->to_string(); + + if (found->second->get_type() == type::unknown) + { + log.error("remove_property('%s'): not registered", name); + } + + // Install default property handler with original value + found->second = std::make_unique(value); + } + else + { + log.error("remove_property('%s'): not found", name); + } +} + +std::string cfg::registry::to_string(const std::string& name) const +{ + reader_lock lock(m_mutex); + + const auto found = m_map.find(name); + + if (found != m_map.end()) + { + auto&& value = found->second->to_string(); + log.trace("to_string('%s'): read value %s", name, value); + return value; + } + + log.warning("to_string('%s'): not found", name); + return{}; +} + +cfg::type cfg::registry::get_type(const std::string& name) const +{ + reader_lock lock(m_mutex); + + const auto found = m_map.find(name); + + if (found != m_map.end()) + { + const type type = found->second->get_type(); + log.trace("get_type('%s'): obtained type %d", name, type); + return type; + } + + log.warning("get_type('%s'): not found", name); + return type::unknown; +} + +std::vector cfg::registry::get_values(const std::string& name) const +{ + reader_lock lock(m_mutex); + + const auto found = m_map.find(name); + + if (found != m_map.end()) + { + auto&& values = found->second->get_values(); + log.trace("get_values('%s'): obtained values (size=%zu)", name, values.size()); + return values; + } + + log.warning("get_values('%s'): not found", name); + return{}; +} + +bool cfg::registry::from_string(const std::string& name, const std::string& value) +{ + std::lock_guard lock(m_mutex); + + if (auto& prop = m_map[name]) + { + const bool result = prop->from_string(value); + log.trace("from_string('%s'): %s with value %s", name, result ? "succeeded" : "failed", value); + return result; + } + else + { + // Create raw property if not registered + log.notice("from_string('%s'): raw property created with value %s", name, value); + prop = std::make_unique(value); + } + + return true; +} + +void cfg::save() +{ + get_registry().save(); +} + +void cfg::register_property(const std::string& name, std::unique_ptr data) +{ + get_registry().set_property_handler(name, std::move(data)); +} + +void cfg::unregister_property(const std::string& name) +{ + get_registry().remove_property_handler(name); +} + +std::string cfg::to_string(const std::string& name) +{ + return get_registry().to_string(name); +} + +bool cfg::to_bool(const std::string& name, bool def) +{ + const std::string& value = get_registry().to_string(name); + + if (value == "false") + return false; + if (value == "true") + return true; + + return def; +} + +cfg::type cfg::get_type(const std::string& name) +{ + return get_registry().get_type(name); +} + +std::vector cfg::get_values(const std::string& name) +{ + return get_registry().get_values(name); +} + +std::size_t cfg::to_index(const std::string& name, std::size_t def) +{ + const std::vector& list = get_registry().get_values(name); + const std::string& value = get_registry().to_string(name); + + for (std::size_t i = 0; i < list.size(); i++) + { + if (list[i] == value) + { + return i; + } + } + + return def; +} + +bool cfg::from_string(const std::string& name, const std::string& value) +{ + return get_registry().from_string(name, value); +} + +bool cfg::from_bool(const std::string& name, bool value) +{ + return get_registry().from_string(name, value ? "true" : "false"); +} + +bool cfg::from_index(const std::string& name, std::size_t index) +{ + return get_registry().from_string(name, get_registry().get_values(name).at(index)); +} + +bool cfg::try_to_int64(s64* out, const std::string& value, s64 min, s64 max) +{ + // TODO: could be rewritten without exceptions (but it shall be as safe as possible and provide logs) + s64 result; + std::size_t pos; + + try + { + result = std::stoll(value, &pos, 0 /* Auto-detect numeric base */); + } + catch (const std::exception& e) + { + if (out) log.error("cfg::try_to_int('%s'): exception: %s", value, e.what()); + return false; + } + + if (pos != value.size()) + { + if (out) log.error("cfg::try_to_int('%s'): unexpected characters (pos=%zu)", value, pos); + return false; + } + + if (result < min || result > max) + { + if (out) log.error("cfg::try_to_int('%s'): out of bounds (%lld..%lld)", value, min, max); + return false; + } + + if (out) *out = result; + return true; +} diff --git a/Utilities/Registry.h b/Utilities/Registry.h new file mode 100644 index 000000000000..09e15e743ff1 --- /dev/null +++ b/Utilities/Registry.h @@ -0,0 +1,640 @@ +#pragma once + +#include "SharedMutex.h" + +namespace cfg +{ + struct property_base; + + // Property type classifier + enum class type : uint + { + unknown = 0, // type for raw_property only ("unregistered" properties) + enumeration, // fixed set of valid values (can be obtained by get_values() call) + boolean, // usually associated with bool type, string values are "false" or "true" + string, // unrestricted string value (must be used for any unclassified exotic type) + int64, // signed integer + }; + + class registry + { + public: + // Configuration map type (values only) + using map_type = std::unordered_map; + + // Configuration layer (TODO) + struct layer + { + map_type prev; // Previous configuration + fs::file file; + }; + + private: + mutable shared_mutex m_mutex; + + // Registered property handlers + std::unordered_map> m_map; + + // Main config file + fs::file m_main_file; + + // Supplementary configuration layers + std::stack m_layers; + + public: + // Initialization + registry(const std::string& path); + void save(); + + /* Serialization */ + + static map_type deserialize(const std::string& text); + static std::string serialize(const map_type& map); + + /* Property registration functions */ + + void set_property_handler(const std::string& name, std::unique_ptr); + void remove_property_handler(const std::string& name); + + /* Property access functions */ + + std::string to_string(const std::string& name) const; + type get_type(const std::string& name) const; + std::vector get_values(const std::string& name) const; + bool from_string(const std::string& name, const std::string& value); + }; + + // Register property with full name and property handler + void register_property(const std::string& name, std::unique_ptr); + + // Unregister property + void unregister_property(const std::string& name); + + // Save configuration + void save(); + + // Get property value (string) + std::string to_string(const std::string& name); + + // Get property value (bool) + bool to_bool(const std::string& name, bool def = false); + + // Get property type + type get_type(const std::string& name); + + // Get list of valid property values + std::vector get_values(const std::string& name); + + // Get property value index + std::size_t to_index(const std::string& name, std::size_t def = 0); + + // Set string value + bool from_string(const std::string& name, const std::string& value); + + // Set bool value + bool from_bool(const std::string& name, bool value); + + // Set value by index + bool from_index(const std::string& name, std::size_t index); + + // Property handler virtual abstract base class + struct property_base + { + virtual ~property_base() = default; + + // Set value from string (default if failed) + explicit_bool_t operator =(const std::string& value) + { + return from_string(value) || (from_default(), false); + } + + // Get property type (type::unknown for raw_property, anything else for other derivatives) + virtual type get_type() const = 0; + + // Get list of valid property values (optional) + virtual std::vector get_values() const + { + return{}; + } + + // Convert to string + virtual std::string to_string() const = 0; + + // Check whether the string is a valid value + virtual bool validate(const std::string& value) const = 0; + + // Try to set value from string + virtual bool from_string(const std::string& value) = 0; + + // Set default value + virtual void from_default() = 0; + + // Check whether the current value is equal to default one + virtual bool is_default() const = 0; + }; + + // Aux conversion function: try to get integer value + bool try_to_int64(s64* result, const std::string& value, s64 min = INT64_MIN, s64 max = INT64_MAX); + + // Aux conversion function: try to get two integer values + template + bool try_to_int_pair(CT* result, const std::string& value, s64 min = std::numeric_limits::min(), s64 max = std::numeric_limits::max()) + { + const auto dpos = value.find(Delimiter); + + s64 v1, v2; + + if (value.empty() || + !try_to_int64(result ? &v1 : nullptr, value.substr(0, dpos), min, max) || + !try_to_int64(result ? &v2 : nullptr, value.substr(dpos + 1, value.size() - (dpos + 1)), min, max)) + { + return false; + } + + if (result) *result = CT{ static_cast(v1), static_cast(v2) }; + return true; + } + + template + struct map_entry final + { + using init_type = std::initializer_list>; + using map_type = std::unordered_map; + using iterator = typename map_type::const_iterator; + using list_type = std::vector; + + static map_type make_map(init_type init) + { + map_type map(init.size()); + + for (const auto& v : init) + { + // Ensure elements are unique + ASSERT(map.emplace(v.first, v.second).second); + } + + return map; + } + + static list_type make_list(init_type init) + { + list_type list; list.reserve(init.size()); + + for (const auto& v : init) + { + list.emplace_back(v.first); + } + + return list; + } + + private: + iterator m_value; + + public: + const std::string name; + const map_type map; // for search + const list_type list; // elements sorted in original order provided + const iterator def; // default value + + // Overload with a default value specified as a string + map_entry(const std::string& name, const std::string& def, init_type init) + : name(name) + , map(make_map(init)) + , list(make_list(init)) + , def(map.find(def)) + { + // Check default value + if (this->def == map.end()) throw EXCEPTION("cfg::map_entry('%s'): invalid default value '%s'", name, def); + + // Set initial value to default + m_value = this->def; + + struct property : property_base + { + map_entry& entry; + + property(map_entry* ptr) + : entry(*ptr) + { + } + + type get_type() const override + { + return type::enumeration; + } + + std::vector get_values() const override + { + return entry.list; + } + + std::string to_string() const override + { + return entry.m_value->first; + } + + bool validate(const std::string& value) const override + { + return entry.map.count(value) != 0; + } + + bool from_string(const std::string& value) override + { + const auto found = entry.map.find(value); + + if (found == entry.map.end()) + { + return false; + } + else + { + entry.m_value = found; + return true; + } + } + + void from_default() override + { + entry.m_value = entry.def; + } + + bool is_default() const override + { + return entry.m_value == entry.def; + } + }; + + register_property(name, std::make_unique(this)); + } + + // Overload with a default value specified as the index + map_entry(const std::string& name, std::size_t def, init_type list) + : map_entry(name, def < list.size() ? (list.begin() + def)->first : throw EXCEPTION("cfg::map_entry('%s'): invalid default value index %zu", name, def), list) + { + } + + // Overload with a default value as first value + map_entry(const std::string& name, init_type list) + : map_entry(name, 0, list) + { + } + + ~map_entry() + { + unregister_property(name); + } + + const T& get() const + { + return m_value->second; + } + + std::string to_string() const + { + return m_value->first; + } + + // Value cannot be changed in current implementation + }; + + template + struct enum_entry final + { + std::atomic value; + const T def; + const std::string name; + + enum_entry(const std::string& name, T def = {}) + : value(def) + , name(name) + , def(def) + { + struct property : property_base + { + enum_entry& entry; + const enum_map& emap = get_enum_map(); + + property(enum_entry* ptr) + : entry(*ptr) + { + } + + type get_type() const override + { + return type::enumeration; + } + + std::vector get_values() const override + { + return emap.list; + } + + std::string to_string() const override + { + return emap[entry.value]; + } + + bool validate(const std::string& value) const override + { + return emap.rmap.count(value) != 0; + } + + bool from_string(const std::string& value) override + { + const auto found = emap.rmap.find(value); + + if (found != emap.rmap.end()) + { + entry.value = found->second; + return true; + } + + return false; + } + + void from_default() override + { + entry.value = entry.def; + } + + bool is_default() const override + { + return entry.value == entry.def; + } + }; + + register_property(name, std::make_unique(this)); + } + + ~enum_entry() + { + unregister_property(name); + } + + operator T() const + { + return value.load(); + } + + enum_entry& operator =(T value) + { + this->value = value; + return *this; + } + }; + + struct bool_entry final + { + std::atomic value; + const bool def; + const std::string name; + + bool_entry(const std::string& name, bool def = false) + : value(def) + , name(name) + , def(def) + { + struct property : property_base + { + bool_entry& entry; + + property(bool_entry* ptr) + : entry(*ptr) + { + } + + type get_type() const override + { + return type::boolean; + } + + std::string to_string() const override + { + return entry.value.load() ? "true" : "false"; + } + + bool validate(const std::string& value) const override + { + return value == "false" || value == "true"; + } + + bool from_string(const std::string& value) override + { + if (value == "false") + { + entry.value = false; + return true; + } + if (value == "true") + { + entry.value = true; + return true; + } + return false; + } + + void from_default() override + { + entry.value = entry.def; + } + + bool is_default() const override + { + return entry.value == entry.def; + } + }; + + register_property(name, std::make_unique(this)); + } + + ~bool_entry() + { + unregister_property(name); + } + + operator bool() const + { + return value.load(); + } + + bool_entry& operator =(bool value) + { + this->value = value; + return *this; + } + }; + + // Signed 64-bit integer entry (supporting different smaller integer types seems unnecessary) + template + struct int_entry final + { + // Use smaller type if possible + using int_type = std::conditional_t= INT32_MIN && Max <= INT32_MAX, s32, s64>; + + static_assert(Min < Max, "Invalid cfg::int_entry range"); + + std::atomic value; + const int_type def; + const std::string name; + + int_entry(const std::string& name, int_type def = std::min(Max, std::max(Min, 0))) + : value(def) + , name(name) + , def(def) + { + struct property : property_base + { + int_entry& entry; + + property(int_entry* ptr) + : entry(*ptr) + { + } + + type get_type() const override + { + return type::int64; + } + + std::vector get_values() const override + { + return{ std::to_string(Min), std::to_string(Max) }; + } + + std::string to_string() const override + { + return std::to_string(entry.value.load()); + } + + bool validate(const std::string& value) const override + { + return try_to_int64(nullptr, value, Min, Max); + } + + bool from_string(const std::string& value) override + { + s64 result; + if (try_to_int64(&result, value, Min, Max)) + { + entry.value = static_cast(result); + return true; + } + + return false; + } + + void from_default() override + { + entry.value = entry.def; + } + + bool is_default() const override + { + return entry.value == entry.def; + } + }; + + register_property(name, std::make_unique(this)); + } + + ~int_entry() + { + unregister_property(name); + } + + operator int_type() const + { + return value.load(); + } + + int_entry& operator =(int_type value) + { + // TODO: does it need validation there? + this->value = value; + return *this; + } + }; + + class string_entry final + { + mutable std::mutex m_mutex; + std::string m_value; + + public: + const std::string def; + const std::string name; + + string_entry(const std::string& name, const std::string& def = {}) + : m_value(def) + , def(def) + , name(name) + { + struct property : property_base + { + string_entry& entry; + + property(string_entry* ptr) + : entry(*ptr) + { + } + + type get_type() const override + { + return type::string; + } + + std::string to_string() const override + { + return entry; + } + + bool validate(const std::string& value) const override + { + return true; + } + + bool from_string(const std::string& value) override + { + entry = value; + return true; + } + + void from_default() override + { + entry = entry.def; + } + + bool is_default() const override + { + return entry.def.compare(entry) == 0; + } + }; + + register_property(name, std::make_unique(this)); + } + + ~string_entry() + { + unregister_property(name); + } + + operator std::string() const + { + std::lock_guard lock(m_mutex); + return m_value; + } + + std::string get() const + { + return *this; + } + + string_entry& operator =(const std::string& value) + { + std::lock_guard lock(m_mutex); + m_value = value; + return *this; + } + }; +} diff --git a/Utilities/SharedMutex.cpp b/Utilities/SharedMutex.cpp index 728e4e443764..4d69b24ddbf3 100644 --- a/Utilities/SharedMutex.cpp +++ b/Utilities/SharedMutex.cpp @@ -1,10 +1,10 @@ -#include "stdafx.h" #include "SharedMutex.h" +#include "Macro.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); + Expects((old_value & SM_READER_COUNT) != SM_READER_COUNT); std::unique_lock lock(m_mutex); @@ -18,7 +18,7 @@ void shared_mutex::impl_lock_shared(u32 old_value) m_ocv.notify_one(); } - CHECK_ASSERTION(++m_rq_size); + ASSERT(++m_rq_size); // Obtain the reader lock while (!atomic_op(m_ctrl, op_lock_shared)) @@ -26,7 +26,7 @@ void shared_mutex::impl_lock_shared(u32 old_value) m_rcv.wait(lock); } - CHECK_ASSERTION(m_rq_size--); + ASSERT(m_rq_size--); if (m_rq_size == 0) { @@ -37,7 +37,7 @@ void shared_mutex::impl_lock_shared(u32 old_value) 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); + Expects((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); @@ -60,7 +60,7 @@ void shared_mutex::impl_lock_excl(u32 value) // Notify non-zero writer queue size m_ctrl |= SM_WRITER_QUEUE; - CHECK_ASSERTION(++m_wq_size); + ASSERT(++m_wq_size); // Obtain the writer lock while (!atomic_op(m_ctrl, op_lock_excl)) @@ -74,7 +74,7 @@ void shared_mutex::impl_lock_excl(u32 value) m_ocv.wait(lock); } - CHECK_ASSERTION(m_wq_size--); + ASSERT(m_wq_size--); if (m_wq_size == 0) { @@ -85,7 +85,7 @@ void shared_mutex::impl_lock_excl(u32 value) void shared_mutex::impl_unlock_excl(u32 value) { // Throw if was not locked exclusively - CHECK_ASSERTION(value & SM_WRITER_LOCK); + Expects(value & SM_WRITER_LOCK); // Mutex cannot be unlocked before notification because m_ctrl has been changed outside std::lock_guard lock(m_mutex); diff --git a/Utilities/SharedMutex.h b/Utilities/SharedMutex.h index 961ef1dd2387..3a78f243269c 100644 --- a/Utilities/SharedMutex.h +++ b/Utilities/SharedMutex.h @@ -1,5 +1,13 @@ #pragma once +#include +#include +#include +#include + +#include "types.h" +#include "Atomic.h" + //! 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. diff --git a/Utilities/SleepQueue.cpp b/Utilities/SleepQueue.cpp deleted file mode 100644 index d7f432cea6fe..000000000000 --- a/Utilities/SleepQueue.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "stdafx.h" -#include "Emu/CPU/CPUThread.h" - -#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/Utilities/SleepQueue.h b/Utilities/SleepQueue.h index e66a284c657f..7bfa69ba86c8 100644 --- a/Utilities/SleepQueue.h +++ b/Utilities/SleepQueue.h @@ -1,45 +1,73 @@ #pragma once -using sleep_entry_t = class CPUThread; -using sleep_queue_t = std::deque>; +// Tag used in sleep_entry<> constructor +static struct defer_sleep_tag {} constexpr defer_sleep{}; -static struct defer_sleep_t {} const defer_sleep{}; +// Define sleep queue as std::deque with T* pointers, T - thread type +template using sleep_queue = std::deque; -// automatic object handling a thread entry in the sleep queue -class sleep_queue_entry_t final +// Automatic object handling a thread pointer (T*) in the sleep queue +// Sleep is called in the constructor (if not null) +// Awake is called in the destructor (if not null) +// Sleep queue is actually std::deque with pointers, be careful about the lifetime +template +class sleep_entry final { - sleep_entry_t& m_thread; - sleep_queue_t& m_queue; - - void add_entry(); - void remove_entry(); - bool find() const; + sleep_queue& m_queue; + T& m_thread; public: - // add specified thread to the sleep queue - sleep_queue_entry_t(sleep_entry_t& entry, sleep_queue_t& queue); + // Constructor; enter() not called + sleep_entry(sleep_queue& queue, T& entry, const defer_sleep_tag&) + : m_queue(queue) + , m_thread(entry) + { + if (Sleep) (m_thread.*Sleep)(); + } - // 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&); + // Constructor; calls enter() + sleep_entry(sleep_queue& queue, T& entry) + : sleep_entry(queue, entry, defer_sleep) + { + enter(); + } - // removes specified thread from the sleep queue if added - ~sleep_queue_entry_t(); + // Destructor; calls leave() + ~sleep_entry() + { + leave(); + if (Awake) (m_thread.*Awake)(); + } - // add thread to the sleep queue + // Add thread to the sleep queue void enter() { - add_entry(); + for (auto t : m_queue) + { + if (t == &m_thread) + { + // Already exists, is it an error? + return; + } + } + + m_queue.emplace_back(&m_thread); } - // remove thread from the sleep queue + // Remove thread from the sleep queue void leave() { - remove_entry(); + auto it = std::find(m_queue.begin(), m_queue.end(), &m_thread); + + if (it != m_queue.end()) + { + m_queue.erase(it); + } } - // check whether the thread exists in the sleep queue + // Check whether the thread exists in the sleep queue explicit operator bool() const { - return find(); + return std::find(m_queue.begin(), m_queue.end(), &m_thread) != m_queue.end(); } }; diff --git a/Utilities/StrFmt.cpp b/Utilities/StrFmt.cpp index 8e6761657991..1b7df25a4df0 100644 --- a/Utilities/StrFmt.cpp +++ b/Utilities/StrFmt.cpp @@ -1,9 +1,5 @@ -#include "stdafx.h" -#pragma warning(push) -#pragma message("TODO: remove wx dependency: ") -#pragma warning(disable : 4996) -#include -#pragma warning(pop) +#include "StrFmt.h" +#include "BEType.h" std::string v128::to_hex() const { @@ -19,7 +15,7 @@ std::string fmt::to_hex(u64 value, u64 count) { if (count - 1 >= 16) { - throw EXCEPTION("Invalid count: 0x%llx", count); + throw exception("fmt::to_hex(): invalid count: 0x%llx", count); } count = std::max(count, 16 - cntlz64(value) / 4); @@ -78,8 +74,6 @@ std::string fmt::to_sdec(s64 svalue) 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); @@ -104,83 +98,6 @@ std::string fmt::replace_all(const std::string &src, const std::string& from, co return target; } -//TODO: move this wx Stuff somewhere else -//convert a wxString to a std::string encoded in utf8 -//CAUTION, only use this to interface with wxWidgets classes -std::string fmt::ToUTF8(const wxString& right) -{ - auto ret = std::string(((const char *)right.utf8_str())); - return ret; -} - -//convert a std::string encoded in utf8 to a wxString -//CAUTION, only use this to interface with wxWidgets classes -wxString fmt::FromUTF8(const std::string& right) -{ - auto ret = wxString::FromUTF8(right.c_str()); - return ret; -} - -//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; @@ -222,21 +139,7 @@ std::string fmt::trim(const std::string& source, const std::string& values) 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) +std::string fmt::escape(const std::string& source, std::initializer_list more) { const std::pair escape_list[] = { @@ -244,20 +147,66 @@ std::string fmt::escape(std::string source) { "\a", "\\a" }, { "\b", "\\b" }, { "\f", "\\f" }, - { "\n", "\\n\n" }, + { "\n", "\\n" }, { "\r", "\\r" }, { "\t", "\\t" }, { "\v", "\\v" }, }; - source = fmt::replace_all(source, escape_list); + std::string result = 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)); + result = fmt::replace_all(result, std::string(1, c), fmt::format("\\x%02X", c)); + } + + for (char c : more) + { + result = fmt::replace_all(result, std::string(1, c), fmt::format("\\x%02X", c)); + } + + return result; +} + +std::string fmt::unescape(const std::string& source) +{ + std::string result; + + for (auto it = source.begin(); it != source.end();) + { + const char bs = *it++; + + if (bs == '\\' && it != source.end()) + { + switch (const char code = *it++) + { + case 'a': result += '\a'; break; + case 'b': result += '\b'; break; + case 'f': result += '\f'; break; + case 'n': result += '\n'; break; + case 'r': result += '\r'; break; + case 't': result += '\t'; break; + case 'v': result += '\v'; break; + case 'x': + { + // Detect hexadecimal character code (TODO) + if (source.end() - it >= 2) + { + result += std::stoi(std::string{ *it++, *it++ }, 0, 16); + } + + } + // Octal/unicode not supported + default: result += code; + } + } + else + { + result += bs; + } } - return source; + return result; } bool fmt::match(const std::string &source, const std::string &mask) diff --git a/Utilities/StrFmt.h b/Utilities/StrFmt.h index 02f544a58b2a..31f240d6c72a 100644 --- a/Utilities/StrFmt.h +++ b/Utilities/StrFmt.h @@ -1,95 +1,38 @@ #pragma once -class wxString; +#include +#include +#include +#include +#include + +#include "Platform.h" +#include "types.h" #if defined(_MSC_VER) && _MSC_VER <= 1800 #define snprintf _snprintf #endif -namespace fmt +// Copy null-terminated string from std::string to char array with truncation +template +inline void strcpy_trunc(char(&dst)[N], const std::string& src) { - //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); - } + const std::size_t count = src.size() >= N ? N - 1 : src.size(); + std::memcpy(dst, src.c_str(), count); + dst[count] = '\0'; +} - // 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; - //} +// Copy null-terminated string from char array to another 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'; +} +namespace fmt +{ 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); @@ -145,7 +88,8 @@ namespace fmt std::string to_udec(u64 value); std::string to_sdec(s64 value); - template::value> struct unveil + template + struct unveil { using result_type = T; @@ -155,37 +99,41 @@ namespace fmt } }; - template<> struct unveil + template<> + struct unveil { using result_type = const char* const; - force_inline static result_type get_value(const char* const& arg) + static result_type get_value(const char* const& arg) { return arg; } }; - template struct unveil + template + struct unveil { using result_type = const char* const; - force_inline static result_type get_value(const char(&arg)[N]) + static result_type get_value(const char(&arg)[N]) { return arg; } }; - template<> struct unveil + template<> + struct unveil { using result_type = const char*; - force_inline static result_type get_value(const std::string& arg) + static result_type get_value(const std::string& arg) { return arg.c_str(); } }; - template struct unveil + template + struct unveil::value>> { using result_type = std::underlying_type_t; @@ -195,16 +143,6 @@ namespace fmt } }; - 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) { @@ -256,51 +194,19 @@ namespace fmt } } - struct exception : public std::exception + // Create exception of type T (std::runtime_error by default) with formatting + template + never_inline safe_buffers T exception(const char* fmt, const Args&... args) noexcept(noexcept(T{ fmt })) { - 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(); - } - }; - - //convert a wxString to a std::string encoded in utf8 - //CAUTION, only use this to interface with wxWidgets classes - std::string ToUTF8(const wxString& right); - - //convert a std::string encoded in utf8 to a wxString - //CAUTION, only use this to interface with wxWidgets classes - wxString FromUTF8(const std::string& right); - - //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); + return T{ format(fmt, do_unveil(args)...).c_str() }; + } - std::vector rSplit(const std::string& source, const std::string& delim); + // Create exception of type T (std::runtime_error by default) without formatting + template + safe_buffers T exception(const char* msg) noexcept(noexcept(T{ msg })) + { + return T{ msg }; + } 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"); @@ -352,8 +258,35 @@ namespace fmt return result; } - std::string tolower(std::string source); - std::string toupper(std::string source); - std::string escape(std::string source); + template + std::string to_lower(IT _begin, IT _end) + { + std::string result; result.resize(_end - _begin); + std::transform(_begin, _end, result.begin(), ::tolower); + return result; + } + + template + std::string to_lower(const T& string) + { + return to_lower(std::begin(string), std::end(string)); + } + + template + std::string to_upper(IT _begin, IT _end) + { + std::string result; result.resize(_end - _begin); + std::transform(_begin, _end, result.begin(), ::toupper); + return result; + } + + template + std::string to_upper(const T& string) + { + return to_upper(std::begin(string), std::end(string)); + } + + std::string escape(const std::string& source, std::initializer_list more = {}); + std::string unescape(const std::string& source); bool match(const std::string &source, const std::string &mask); } diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index 61619466a3bd..e8659b86e6e8 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -1,11 +1,8 @@ #include "stdafx.h" -#include "Log.h" +#include "Emu/Memory/Memory.h" #include "Emu/System.h" -#include "Emu/state.h" #include "Emu/CPU/CPUThreadManager.h" -#include "Emu/CPU/CPUThread.h" #include "Emu/Cell/RawSPUThread.h" -#include "Emu/SysCalls/SysCalls.h" #include "Thread.h" #ifdef _WIN32 @@ -34,9 +31,9 @@ static void report_fatal_error(const std::string& msg) { throw; } - catch (const std::exception& ex) + catch (const std::exception& e) { - report_fatal_error("Unhandled exception: "s + ex.what()); + report_fatal_error("Unhandled exception of type '"s + typeid(e).name() + "': "s + e.what()); } catch (...) { @@ -1332,7 +1329,7 @@ thread_ctrl::~thread_ctrl() std::string thread_ctrl::get_name() const { - CHECK_ASSERTION(m_name); + ASSERT(m_name); return m_name(); } @@ -1344,10 +1341,10 @@ std::string named_thread_t::get_name() const void named_thread_t::start() { - CHECK_ASSERTION(!m_thread); + Expects(!m_thread); // Get shared_ptr instance (will throw if called from the constructor or the object has been created incorrectly) - auto ptr = shared_from_this(); + auto&& ptr = shared_from_this(); // Make name getter auto name = [wptr = std::weak_ptr(ptr), type = &typeid(*this)]() @@ -1369,14 +1366,12 @@ void named_thread_t::start() try { LOG_TRACE(GENERAL, "Thread started"); - thread->on_task(); - LOG_TRACE(GENERAL, "Thread ended"); } catch (const std::exception& e) { - LOG_FATAL(GENERAL, "Exception: %s", e.what()); + LOG_FATAL(GENERAL, "%s thrown: %s", typeid(e).name(), e.what()); Emu.Pause(); } catch (EmulationStopped) @@ -1390,7 +1385,7 @@ void named_thread_t::start() void named_thread_t::join() { - CHECK_ASSERTION(m_thread != nullptr); + Expects(m_thread != nullptr); try { diff --git a/Utilities/Thread.h b/Utilities/Thread.h index 557a83d7a072..15136c8efbc0 100644 --- a/Utilities/Thread.h +++ b/Utilities/Thread.h @@ -57,7 +57,7 @@ class thread_ctrl final template static inline void at_exit(T&& func) { - CHECK_ASSERTION(g_tls_this_thread); + Expects(g_tls_this_thread); g_tls_this_thread->m_atexit.emplace_front(std::forward(func)); } @@ -141,7 +141,7 @@ class named_thread_t : public std::enable_shared_from_this bool is_started() const { return m_thread.operator bool(); } // Compare with the current thread - bool is_current() const { CHECK_ASSERTION(m_thread); return thread_ctrl::get_current() == m_thread.get(); } + bool is_current() const { Expects(m_thread); return thread_ctrl::get_current() == m_thread.get(); } // Get thread_ctrl const thread_ctrl* get_thread_ctrl() const { return m_thread.get(); } @@ -177,6 +177,7 @@ extern const std::function SQUEUE_NEVER_EXIT; bool squeue_test_exit(); +// TODO: eliminate this boolshit template class squeue_t { @@ -232,8 +233,8 @@ class squeue_t while (u32 res = m_sync.atomic_op([&pos](squeue_sync_var_t& sync) -> u32 { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); + Expects(sync.count <= sq_size); + Expects(sync.position < sq_size); if (sync.push_lock) { @@ -262,9 +263,9 @@ class squeue_t m_sync.atomic_op([](squeue_sync_var_t& sync) { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); - assert(sync.push_lock); + Expects(sync.count <= sq_size); + Expects(sync.position < sq_size); + Expects(sync.push_lock); sync.push_lock = 0; sync.count++; }); @@ -295,8 +296,8 @@ class squeue_t while (u32 res = m_sync.atomic_op([&pos](squeue_sync_var_t& sync) -> u32 { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); + Expects(sync.count <= sq_size); + Expects(sync.position < sq_size); if (!sync.count) { @@ -325,9 +326,9 @@ class squeue_t m_sync.atomic_op([](squeue_sync_var_t& sync) { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); - assert(sync.pop_lock); + Expects(sync.count <= sq_size); + Expects(sync.position < sq_size); + Expects(sync.pop_lock); sync.pop_lock = 0; sync.position++; sync.count--; @@ -359,13 +360,13 @@ class squeue_t bool peek(T& data, u32 start_pos, const std::function& test_exit) { - assert(start_pos < sq_size); + Expects(start_pos < sq_size); u32 pos = 0; while (u32 res = m_sync.atomic_op([&pos, start_pos](squeue_sync_var_t& sync) -> u32 { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); + Expects(sync.count <= sq_size); + Expects(sync.position < sq_size); if (sync.count <= start_pos) { @@ -394,9 +395,9 @@ class squeue_t m_sync.atomic_op([](squeue_sync_var_t& sync) { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); - assert(sync.pop_lock); + Expects(sync.count <= sq_size); + Expects(sync.position < sq_size); + Expects(sync.pop_lock); sync.pop_lock = 0; }); @@ -435,7 +436,7 @@ class squeue_t public: T& operator [] (u32 index) { - assert(index < m_count); + Expects(index < m_count); index += m_pos; index = index < sq_size ? index : index - sq_size; return m_data[index]; @@ -448,8 +449,8 @@ class squeue_t while (m_sync.atomic_op([&pos, &count](squeue_sync_var_t& sync) -> u32 { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); + Expects(sync.count <= sq_size); + Expects(sync.position < sq_size); if (sync.pop_lock || sync.push_lock) { @@ -471,9 +472,9 @@ class squeue_t m_sync.atomic_op([](squeue_sync_var_t& sync) { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); - assert(sync.pop_lock && sync.push_lock); + Expects(sync.count <= sq_size); + Expects(sync.position < sq_size); + Expects(sync.pop_lock && sync.push_lock); sync.pop_lock = 0; sync.push_lock = 0; }); @@ -486,8 +487,8 @@ class squeue_t { while (m_sync.atomic_op([](squeue_sync_var_t& sync) -> u32 { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); + Expects(sync.count <= sq_size); + Expects(sync.position < sq_size); if (sync.pop_lock || sync.push_lock) { diff --git a/Utilities/VirtualMemory.cpp b/Utilities/VirtualMemory.cpp index 5a2d00e90171..e0b2b2ed61f8 100644 --- a/Utilities/VirtualMemory.cpp +++ b/Utilities/VirtualMemory.cpp @@ -17,10 +17,10 @@ namespace memory_helper { #ifdef _WIN32 void* ret = VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_NOACCESS); - CHECK_ASSERTION(ret != NULL); + Ensures(ret != NULL); #else void* ret = mmap(nullptr, size, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0); - CHECK_ASSERTION(ret != 0); + Ensures(ret != 0); #endif return ret; } @@ -28,18 +28,18 @@ namespace memory_helper void commit_page_memory(void* pointer, size_t page_size) { #ifdef _WIN32 - CHECK_ASSERTION(VirtualAlloc((u8*)pointer, page_size, MEM_COMMIT, PAGE_READWRITE) != NULL); + ASSERT(VirtualAlloc((u8*)pointer, page_size, MEM_COMMIT, PAGE_READWRITE) != NULL); #else - CHECK_ASSERTION(mprotect((u8*)pointer, page_size, PROT_READ | PROT_WRITE) != -1); + ASSERT(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); + ASSERT(VirtualFree(pointer, 0, MEM_RELEASE) != 0); #else - CHECK_ASSERTION(munmap(pointer, size) == 0); + ASSERT(munmap(pointer, size) == 0); #endif } } diff --git a/Utilities/config_context.cpp b/Utilities/config_context.cpp deleted file mode 100644 index 69b953d19d84..000000000000 --- a/Utilities/config_context.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include "stdafx.h" -#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); -} diff --git a/Utilities/config_context.h b/Utilities/config_context.h deleted file mode 100644 index 3263a385326f..000000000000 --- a/Utilities/config_context.h +++ /dev/null @@ -1,164 +0,0 @@ -#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 ~entry_base() = default; - 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/Utilities/convert.h b/Utilities/convert.h deleted file mode 100644 index b190737fcb03..000000000000 --- a/Utilities/convert.h +++ /dev/null @@ -1,279 +0,0 @@ -#pragma once -#include -#include "types.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 : value == "false" ? false : throw std::invalid_argument(__FUNCTION__); - } - }; - - template<> - struct to_impl_t - { - static std::string func(signed 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/Utilities/rPlatform.cpp b/Utilities/rPlatform.cpp deleted file mode 100644 index 53f7e783d721..000000000000 --- a/Utilities/rPlatform.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "stdafx.h" -#include "restore_new.h" -#include "Utilities/Log.h" -#pragma warning(push) -#pragma message("TODO: remove wx dependency: ") -#pragma warning(disable : 4996) -#include -#pragma warning(pop) -#include "define_new_memleakdetect.h" - -#ifndef _WIN32 -#include -#endif - -#include "rPlatform.h" - -rImage::rImage() -{ - handle = static_cast(new wxImage()); -} - -rImage::~rImage() -{ - delete static_cast(handle); -} - -void rImage::Create(int width, int height, void *data, void *alpha) -{ - static_cast(handle)->Create(width, height, static_cast(data), static_cast(alpha)); -} -void rImage::SaveFile(const std::string& name, rImageType type) -{ - if (type == rBITMAP_TYPE_PNG) - { - static_cast(handle)->SaveFile(fmt::FromUTF8(name),wxBITMAP_TYPE_PNG); - } - else - { - throw EXCEPTION("unsupported type"); - } -} diff --git a/Utilities/rPlatform.h b/Utilities/rPlatform.h deleted file mode 100644 index 09308edb5357..000000000000 --- a/Utilities/rPlatform.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -/********************************************************************** -*********** RSX Debugger -************************************************************************/ - -struct RSXDebuggerProgram -{ - u32 id; - u32 vp_id; - u32 fp_id; - std::string vp_shader; - std::string fp_shader; - bool modified; - - RSXDebuggerProgram() - : modified(false) - { - } -}; - -extern std::vector m_debug_programs; - -/********************************************************************** -*********** Image stuff -************************************************************************/ -enum rImageType -{ - rBITMAP_TYPE_PNG -}; -struct rImage -{ - rImage(); - rImage(const rImage &) = delete; - ~rImage(); - void Create(int width , int height, void *data, void *alpha); - void SaveFile(const std::string& name, rImageType type); - - void *handle; -}; diff --git a/Utilities/rTime.cpp b/Utilities/rTime.cpp index 968714130203..704745100cd1 100644 --- a/Utilities/rTime.cpp +++ b/Utilities/rTime.cpp @@ -3,6 +3,7 @@ #pragma warning(push) #pragma message("TODO: remove wx dependency: ") #pragma warning(disable : 4996) +#include "stdafx_gui.h" #include #pragma warning(pop) diff --git a/Utilities/rXml.cpp b/Utilities/rXml.cpp index 30e0b457c202..df4e7b3040ef 100644 --- a/Utilities/rXml.cpp +++ b/Utilities/rXml.cpp @@ -3,6 +3,7 @@ #pragma warning(push) #pragma message("TODO: remove wx dependency: ") #pragma warning(disable : 4996) +#include "stdafx_gui.h" #include #pragma warning(pop) diff --git a/Utilities/types.h b/Utilities/types.h index ab011c2e3c97..7f9f19d4f454 100644 --- a/Utilities/types.h +++ b/Utilities/types.h @@ -23,6 +23,45 @@ using s16 = std::int16_t; using s32 = std::int32_t; using s64 = std::int64_t; +#ifndef _MSC_VER +using u128 = __uint128_t; +#endif + +// Bool type replacement for PS3/PSV +class b8 +{ + std::uint8_t m_value; + +public: + b8() = default; + + constexpr b8(bool value) + : m_value(value) + { + } + + constexpr operator bool() const + { + return m_value != 0; + } +}; + +// 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; + } +}; + union alignas(2) f16 { u16 _u16;