diff --git a/cmake/SearchForStuff.cmake b/cmake/SearchForStuff.cmake index 109d8ee951..b061982e62 100644 --- a/cmake/SearchForStuff.cmake +++ b/cmake/SearchForStuff.cmake @@ -252,6 +252,14 @@ else() set(BIN2CPPDEP ${CMAKE_SOURCE_DIR}/linux_various/hex2h.pl) endif() +# rapidyaml includes fast_float as a submodule, saves us pulling it in directly. +# Normally, we'd just pull in the cmake project, and link to it, but... it seems to enable +# permissive mode, which breaks other parts of PCSX2. So, we'll just create a target here +# for now. +#add_subdirectory(3rdparty/rapidyaml/rapidyaml/ext/c4core/src/c4/ext/fast_float EXCLUDE_FROM_ALL) +add_library(fast_float INTERFACE) +target_include_directories(fast_float INTERFACE 3rdparty/rapidyaml/rapidyaml/ext/c4core/src/c4/ext/fast_float/include) + add_subdirectory(3rdparty/jpgd EXCLUDE_FROM_ALL) add_subdirectory(3rdparty/simpleini EXCLUDE_FROM_ALL) add_subdirectory(3rdparty/imgui EXCLUDE_FROM_ALL) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 8d6e2b3c60..51101e6261 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -288,6 +288,7 @@ target_link_libraries(common PRIVATE target_link_libraries(common PUBLIC fmt::fmt + fast_float ) fixup_file_properties(common) diff --git a/common/StringUtil.h b/common/StringUtil.h index 7bc4dda056..b9db36b8f8 100644 --- a/common/StringUtil.h +++ b/common/StringUtil.h @@ -15,6 +15,7 @@ #pragma once #include "Pcsx2Types.h" +#include #include #include #include @@ -24,13 +25,16 @@ #include #include -#if defined(__has_include) && __has_include() -#include -#ifndef _MSC_VER +#include "fast_float/fast_float.h" + +// Older versions of libstdc++ are missing support for from_chars() with floats, and was only recently +// merged in libc++. So, just fall back to stringstream (yuck!) on everywhere except MSVC. +#if !defined(_MSC_VER) +#include #include +#ifdef __APPLE__ +#include #endif -#else -#include #endif namespace StringUtil @@ -72,23 +76,15 @@ namespace StringUtil #endif } - /// Wrapper arond std::from_chars + /// Wrapper around std::from_chars template ::value, bool> = true> inline std::optional FromChars(const std::string_view& str, int base = 10) { T value; -#if defined(__has_include) && __has_include() const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), value, base); if (result.ec != std::errc()) return std::nullopt; -#else - std::string temp(str); - std::istringstream ss(temp); - ss >> std::setbase(base) >> value; - if (ss.fail()) - return std::nullopt; -#endif return value; } @@ -98,22 +94,57 @@ namespace StringUtil { T value; -#if defined(__has_include) && __has_include() && defined(_MSC_VER) - const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), value); + const fast_float::from_chars_result result = fast_float::from_chars(str.data(), str.data() + str.length(), value); if (result.ec != std::errc()) return std::nullopt; -#else - /// libstdc++ does not support from_chars with floats yet - std::string temp(str); - std::istringstream ss(temp); - ss >> value; - if (ss.fail()) - return std::nullopt; -#endif return value; } + /// Wrapper around std::to_chars + template ::value, bool> = true> + inline std::string ToChars(T value, int base = 10) + { + // to_chars() requires macOS 10.15+. +#if !defined(__APPLE__) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15 + constexpr size_t MAX_SIZE = 32; + char buf[MAX_SIZE]; + std::string ret; + + const std::to_chars_result result = std::to_chars(buf, buf + MAX_SIZE, value, base); + if (result.ec == std::errc()) + ret.append(buf, result.ptr - buf); + + return ret; +#else + std::ostringstream ss; + ss.imbue(std::locale::classic()); + ss << std::setbase(base) << value; + return ss.str(); +#endif + } + + template ::value, bool> = true> + inline std::string ToChars(T value) + { + // No to_chars() in older versions of libstdc++/libc++. +#ifdef _MSC_VER + constexpr size_t MAX_SIZE = 64; + char buf[MAX_SIZE]; + std::string ret; + const std::to_chars_result result = std::to_chars(buf, buf + MAX_SIZE, value); + if (result.ec == std::errc()) + ret.append(buf, result.ptr - buf); + return ret; +#else + std::ostringstream ss; + ss.imbue(std::locale::classic()); + ss << value; + return ss.str(); +#endif + } + + /// Explicit override for booleans template <> inline std::optional FromChars(const std::string_view& str, int base) @@ -135,6 +166,12 @@ namespace StringUtil return std::nullopt; } + template <> + inline std::string ToChars(bool value, int base) + { + return std::string(value ? "true" : "false"); + } + /// Encode/decode hexadecimal byte buffers std::optional> DecodeHex(const std::string_view& str); std::string EncodeHex(const u8* data, int length); diff --git a/common/common.vcxproj b/common/common.vcxproj index 8360d43933..0b2f4fb73e 100644 --- a/common/common.vcxproj +++ b/common/common.vcxproj @@ -33,6 +33,7 @@ $(SolutionDir)3rdparty\d3d12memalloc\include;$(SolutionDir)3rdparty\glad\include;$(SolutionDir)3rdparty\glslang\glslang;%(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\libpng %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\jpgd Async diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index 6d3cf7c10b..e2540ccd6b 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -43,6 +43,7 @@ $(SolutionDir)3rdparty\glad\include;%(AdditionalIncludeDirectories) $(SolutionDir)3rdparty\simpleini\include;%(AdditionalIncludeDirectories) $(SolutionDir)3rdparty\lzma\include;%(AdditionalIncludeDirectories) + $(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include;%(AdditionalIncludeDirectories); $(ProjectDir);$(SolutionDir)pcsx2;%(AdditionalIncludeDirectories) %(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList;$(ProjectDir)\Tools\InputRecording diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj index fc96f7e19a..827a88d7f7 100644 --- a/pcsx2/pcsx2.vcxproj +++ b/pcsx2/pcsx2.vcxproj @@ -46,6 +46,7 @@ $(SolutionDir)3rdparty\libzip;$(SolutionDir)3rdparty\libzip\libzip\lib;%(AdditionalIncludeDirectories) $(SolutionDir)3rdparty\d3d12memalloc\include;%(AdditionalIncludeDirectories) %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zstd\zstd\lib + %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include Async Use PrecompiledHeader.h diff --git a/pcsx2/pcsx2core.vcxproj b/pcsx2/pcsx2core.vcxproj index 434ec19457..7b797b3d99 100644 --- a/pcsx2/pcsx2core.vcxproj +++ b/pcsx2/pcsx2core.vcxproj @@ -49,6 +49,7 @@ $(SolutionDir)3rdparty\d3d12memalloc\include;%(AdditionalIncludeDirectories) %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zstd\zstd\lib %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\cpuinfo\include + %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include Async Use PrecompiledHeader.h diff --git a/tests/ctest/common/CMakeLists.txt b/tests/ctest/common/CMakeLists.txt index 974be98089..5eaaa67009 100644 --- a/tests/ctest/common/CMakeLists.txt +++ b/tests/ctest/common/CMakeLists.txt @@ -1 +1 @@ -add_pcsx2_test(common_test path_tests.cpp) +add_pcsx2_test(common_test path_tests.cpp string_util_tests.cpp) diff --git a/tests/ctest/common/string_util_tests.cpp b/tests/ctest/common/string_util_tests.cpp new file mode 100644 index 0000000000..b7b788772b --- /dev/null +++ b/tests/ctest/common/string_util_tests.cpp @@ -0,0 +1,82 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "common/Pcsx2Defs.h" +#include "common/StringUtil.h" +#include +#include +#include + +TEST(StringUtil, ToChars) +{ + ASSERT_EQ(StringUtil::ToChars(false), "false"); + ASSERT_EQ(StringUtil::ToChars(true), "true"); + ASSERT_EQ(StringUtil::ToChars(0), "0"); + ASSERT_EQ(StringUtil::ToChars(-1337), "-1337"); + ASSERT_EQ(StringUtil::ToChars(1337), "1337"); + ASSERT_EQ(StringUtil::ToChars(1337u), "1337"); + ASSERT_EQ(StringUtil::ToChars(13.37f), "13.37"); + ASSERT_EQ(StringUtil::ToChars(255, 16), "ff"); +} + +TEST(StringUtil, FromChars) +{ + ASSERT_EQ(StringUtil::FromChars("false").value_or(true), false); + ASSERT_EQ(StringUtil::FromChars("true").value_or(false), true); + ASSERT_EQ(StringUtil::FromChars("0").value_or(-1), 0); + ASSERT_EQ(StringUtil::FromChars("-1337").value_or(0), -1337); + ASSERT_EQ(StringUtil::FromChars("1337").value_or(0), 1337); + ASSERT_EQ(StringUtil::FromChars("1337").value_or(0), 1337); + ASSERT_TRUE(std::abs(StringUtil::FromChars("13.37").value_or(0.0f) - 13.37) < 0.01f); + ASSERT_EQ(StringUtil::FromChars("ff", 16).value_or(0), 255); +} + +#if 0 +// NOTE: These tests are disabled, because they require the da_DK locale to actually be present. +// Which probably isn't going to be the case on the CI. + +TEST(StringUtil, ToCharsIsLocaleIndependent) +{ + const auto old_locale = std::locale(); + std::locale::global(std::locale::classic()); + + std::string classic_result(StringUtil::ToChars(13.37f)); + + std::locale::global(std::locale("da_DK")); + + std::string dk_result(StringUtil::ToChars(13.37f)); + + std::locale::global(old_locale); + + ASSERT_EQ(classic_result, dk_result); +} + +TEST(StringUtil, FromCharsIsLocaleIndependent) +{ + const auto old_locale = std::locale(); + std::locale::global(std::locale::classic()); + + const float classic_result = StringUtil::FromChars("13.37").value_or(0.0f); + + std::locale::global(std::locale("da_DK")); + + const float dk_result = StringUtil::FromChars("13.37").value_or(0.0f); + + std::locale::global(old_locale); + + ASSERT_EQ(classic_result, dk_result); +} + +#endif diff --git a/updater/updater.vcxproj b/updater/updater.vcxproj index 6b2ce5ba39..9bc76622d5 100644 --- a/updater/updater.vcxproj +++ b/updater/updater.vcxproj @@ -33,6 +33,7 @@ $(SolutionDir)3rdparty\lzma\include;%(AdditionalIncludeDirectories) + $(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include;%(AdditionalIncludeDirectories) $(ProjectDir);%(AdditionalIncludeDirectories) Async NotUsing