diff --git a/.clang-format b/.clang-format index 4a056c2c..e76203c7 100644 --- a/.clang-format +++ b/.clang-format @@ -26,7 +26,6 @@ BinPackArguments: true BinPackParameters: true BitFieldColonSpacing: None BreakBeforeBinaryOperators: NonAssignment -BreakBeforeBraces: Custom BreakBeforeConceptDeclarations: true BreakConstructorInitializers: BeforeComma BreakInheritanceList: AfterComma @@ -62,6 +61,7 @@ IndentPPDirectives: BeforeHash IndentRequires: true IndentWidth: 4 IndentWrappedFunctionNames: false +InsertBraces: true KeepEmptyLinesAtTheStartOfBlocks: false LambdaBodyIndentation: OuterScope MaxEmptyLinesToKeep: 2 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a3cf9733..82d7c5e1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,34 +12,31 @@ on: jobs: Build: - name: ${{ matrix.os-name }} (${{ matrix.compiler }}, ${{ matrix.config }}) + name: ${{ matrix.os }} (${{ matrix.compiler }}, ${{ matrix.config }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: config: [Release, Debug] - name: [windows-msvc, windows-clang, linux-clang, linux-gcc, macos-clang] - include: - - name: windows-msvc - os: windows-latest - os-name: Windows + os: + - windows-2022 + - ubuntu-24.04 + - macos-15 + compiler: + - clang-18 + - gcc-14 + - msvc + exclude: + - os: windows-2022 + compiler: gcc-14 + - os: ubuntu-24.04 compiler: msvc - - name: windows-clang - os: windows-latest - os-name: Windows - compiler: clang-17 - - name: linux-clang - os: ubuntu-latest - os-name: Linux - compiler: clang-16 - - name: linux-gcc - os: ubuntu-latest - os-name: Linux - compiler: gcc-13 - - name: macos-clang - os: macos-14 - os-name: MacOS - compiler: clang-17 + - os: macos-15 + compiler: msvc + - os: macos-15 + compiler: clang-18 + env: + os-name: ${{ contains(matrix.os, 'windows') && 'Windows' || (contains(matrix.os, 'ubuntu') && 'Linux' || 'MacOS') }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -49,13 +46,14 @@ jobs: - name: Setup Cpp uses: aminya/setup-cpp@v1 with: - compiler: ${{ (matrix.os-name != 'MacOS' || !contains(matrix.compiler, 'clang')) && matrix.compiler || '' }} - vcvarsall: ${{ matrix.os-name == 'Windows' }} + # Skip compiler setup for macos clang-17 + compiler: ${{ (contains(matrix.os, 'macos') && matrix.compiler == 'clang-17') && '' || matrix.compiler }} + vcvarsall: ${{ contains(matrix.os, 'windows') }} cmake: true ninja: true - - name: (MacOS) Install clang through brew - if: matrix.os-name == 'MacOS' && contains(matrix.compiler, 'clang') + - name: (MacOS) Install clang-17 through brew + if: contains(matrix.os, 'macos') && matrix.compiler == 'clang-17' run: | brew install llvm@17 export LLVM_DIR="$(brew --prefix llvm@17)/lib/cmake" @@ -83,11 +81,11 @@ jobs: uses: actions/upload-artifact@v4 if: ${{ contains(matrix.compiler, 'clang') }} # Only clang artifacts are stored with: - name: Pipe-${{ matrix.os-name }}-${{ matrix.config }} + name: Pipe-${{ env.os-name }}-${{ matrix.config }} path: Install - name: Run Tests - if: ${{ matrix.os-name != 'macos' }} # Tests are skipped temporarily on MacOS + if: ${{ !contains(matrix.os, 'macos') }} # Tests are skipped temporarily on MacOS working-directory: ./Build run: ctest --output-on-failure -j2 -C ${{ matrix.config }} diff --git a/.gitignore b/.gitignore index 748d41df..64661835 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,3 @@ Saved /compile_commands.json compile_commands.json Install - -Include/Pipe/Export.h diff --git a/Benchmarks/Arenas.bench.h b/Benchmarks/Arenas.bench.h index 7174fa60..83102479 100644 --- a/Benchmarks/Arenas.bench.h +++ b/Benchmarks/Arenas.bench.h @@ -4,12 +4,7 @@ #include "nanobench.h" using namespace ankerl; -#include -#include -#include -#include -#include -#include +#include #include diff --git a/Benchmarks/main.cpp b/Benchmarks/main.cpp index a7342085..408883bb 100644 --- a/Benchmarks/main.cpp +++ b/Benchmarks/main.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2024 Piperift - All rights reserved -#include +#include // Override as first include #define ANKERL_NANOBENCH_IMPLEMENT 1 diff --git a/CMake/Util.cmake b/CMake/Util.cmake index bb2c6c8d..b348ed0a 100644 --- a/CMake/Util.cmake +++ b/CMake/Util.cmake @@ -91,15 +91,23 @@ function(set_option target exposure option) endfunction() function(target_add_link_option_if_compiles target exposure flag) - CHECK_CXX_COMPILER_FLAG("${flag}" COMPILER_HAS_THOSE_TOGGLES) - if(COMPILER_HAS_THOSE_TOGGLES) - target_link_options(${target_name} ${exposure} ${flag}) - endif() + CHECK_CXX_COMPILER_FLAG("${flag}" COMPILER_HAS_THOSE_TOGGLES) + if(COMPILER_HAS_THOSE_TOGGLES) + target_link_options(${target_name} ${exposure} ${flag}) + endif() endfunction() function(target_add_compile_option_if_compiles target exposure flag) - CHECK_CXX_COMPILER_FLAG("${flag}" COMPILER_HAS_THOSE_TOGGLES) - if(COMPILER_HAS_THOSE_TOGGLES) - target_compile_options(${target_name} ${exposure} ${flag}) - endif() + CHECK_CXX_COMPILER_FLAG("${flag}" COMPILER_HAS_THOSE_TOGGLES) + if(COMPILER_HAS_THOSE_TOGGLES) + target_compile_options(${target_name} ${exposure} ${flag}) + endif() endfunction() + +function(pipe_target_disable_rtti target exposure) + if(COMPILER_MSVC) + target_compile_options(${target} ${exposure} /GR-) + elseif(COMPILER_CLANG OR COMPILER_GCC) + target_compile_options(${target} ${exposure} -fno-rtti) + endif() +endfunction() \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index fadc37f0..982ad6b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) ################################################################################ # Project -project(Pipe VERSION 0.1 LANGUAGES CXX) +project(Pipe VERSION 0.1 LANGUAGES CXX C) if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) @@ -38,7 +38,6 @@ endif() include(CMake/Util.cmake) include(CMake/LinkTimeOptimization.cmake) find_package(Sanitizers) -include(GenerateExportHeader) # ############################################################################## @@ -52,18 +51,17 @@ add_subdirectory(Extern) if (PIPE_BUILD_SHARED) add_library(Pipe SHARED) + target_compile_definitions(Pipe PUBLIC PIPE_IMPORTS=1) + target_compile_definitions(Pipe PRIVATE -DPIPE_IMPORTS PIPE_EXPORTS=1) else() add_library(Pipe STATIC) endif() -generate_export_header(Pipe - BASE_NAME PIPE - EXPORT_MACRO_NAME PIPE_API - EXPORT_FILE_NAME ${CMAKE_CURRENT_SOURCE_DIR}/Include/Pipe/Export.h) pipe_target_define_platform(Pipe) target_include_directories(Pipe PUBLIC $) -file(GLOB_RECURSE PIPE_SOURCE_FILES CONFIGURE_DEPENDS Src/*.cpp Src/*.h) +target_include_directories(Pipe PRIVATE $) +file(GLOB_RECURSE PIPE_SOURCE_FILES CONFIGURE_DEPENDS Src/*.cpp Src/*.c) target_sources(Pipe PRIVATE ${PIPE_SOURCE_FILES}) target_compile_definitions(Pipe PRIVATE NOMINMAX) @@ -75,6 +73,7 @@ pipe_target_enable_CPP20(Pipe) pipe_add_sanitizers(Pipe) pipe_target_shared_output_directory(Pipe) #pipe_target_link_time_optimization(Pipe) +pipe_target_disable_rtti(Pipe PRIVATE) ################################################################################ @@ -130,6 +129,6 @@ install(DIRECTORY Include/Pipe ) # Debug files -if (PLATFORM_WINDOWS) +if (PLATFORM_WINDOWS AND NOT COMPILER_GCC) install(FILES $ DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL) endif() diff --git a/Include/Misc/PipeDebug.h b/Include/Misc/PipeDebug.h index 2c87067b..3d3a3ae9 100644 --- a/Include/Misc/PipeDebug.h +++ b/Include/Misc/PipeDebug.h @@ -13,6 +13,8 @@ static_assert(false, "Imgui not found. PipeDebug requires v1.90 or newer."); static_assert(false, "Imgui v" IMGUI_VERSION " found but PipeDebug requires v1.90 or newer."); #endif +// Mark debug tools as present for others +#define P_DEBUG_TOOLS 1 #include "Misc/PipeImGui.h" #include "Pipe/Core/Checks.h" @@ -176,7 +178,8 @@ namespace p EntityContext* ctx = nullptr; - bool initialized = false; + bool initialized = false; + bool isFirstDebug = true; DebugContext() = default; @@ -871,9 +874,13 @@ namespace p ImGui::PushTextColor(errorTextColor); const char* errorMsg; if (inspector.id == NoId) + { errorMsg = "No entity"; + } else + { errorMsg = removed ? "Removed entity" : "Invalid entity"; + } auto regionAvail = ImGui::GetContentRegionAvail(); auto textSize = @@ -1139,7 +1146,9 @@ namespace p ImGui::TableSetColumnIndex(0); // Id if (!passedFilter) + { ImGui::PushTextColor(ImGui::GetTextColor().Shade(0.3f)); + } ImGuiTreeNodeFlags treeNodeFlags = ImGuiTreeNodeFlags_SpanAllColumns | ImGuiTreeNodeFlags_NoTreePushOnOpen; if (typeProperties.IsEmpty()) @@ -1148,7 +1157,9 @@ namespace p } const bool propsOpen = ImGui::TreeNodeEx(idText.data(), treeNodeFlags); if (!passedFilter) + { ImGui::PopTextColor(); + } ImGui::TableSetColumnIndex(1); // Name StringView ns; @@ -1277,7 +1288,8 @@ namespace p "Called EndDebug() but there was no current ECS Debug Context! Forgot " "to call " "BeginDebug()?"); - currentContext = nullptr; + currentContext->isFirstDebug = false; + currentContext = nullptr; } #endif }; // namespace p diff --git a/Include/Pipe.h b/Include/Pipe.h index 0a0d9b61..87cb9670 100644 --- a/Include/Pipe.h +++ b/Include/Pipe.h @@ -8,6 +8,6 @@ namespace p { - PIPE_API void Initialize(StringView logPath = {}); - PIPE_API void Shutdown(); + P_API void Initialize(struct Logger* logger = nullptr); + P_API void Shutdown(); }; // namespace p diff --git a/Include/Pipe/Core/Backward.h b/Include/Pipe/Core/Backward.h deleted file mode 100644 index ab076b20..00000000 --- a/Include/Pipe/Core/Backward.h +++ /dev/null @@ -1,4494 +0,0 @@ -/* - * backward.hpp - * Copyright 2013 Google Inc. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -// clang-format off - -#ifndef H_6B9572DA_A64B_49E6_B234_051480991C89 -#define H_6B9572DA_A64B_49E6_B234_051480991C89 - -#ifndef __cplusplus -#error "It's not going to compile without a C++ compiler..." -#endif - -#if defined(BACKWARD_CXX11) -#elif defined(BACKWARD_CXX98) -#else -#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1800) -#define BACKWARD_CXX11 -#define BACKWARD_ATLEAST_CXX11 -#define BACKWARD_ATLEAST_CXX98 -#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -#define BACKWARD_ATLEAST_CXX17 -#endif -#else -#define BACKWARD_CXX98 -#define BACKWARD_ATLEAST_CXX98 -#endif -#endif - -// You can define one of the following (or leave it to the auto-detection): -// -// #define BACKWARD_SYSTEM_LINUX -// - specialization for linux -// -// #define BACKWARD_SYSTEM_DARWIN -// - specialization for Mac OS X 10.5 and later. -// -// #define BACKWARD_SYSTEM_WINDOWS -// - specialization for Windows (Clang 9 and MSVC2017) -// -// #define BACKWARD_SYSTEM_UNKNOWN -// - placebo implementation, does nothing. -// -#if defined(BACKWARD_SYSTEM_LINUX) -#elif defined(BACKWARD_SYSTEM_DARWIN) -#elif defined(BACKWARD_SYSTEM_UNKNOWN) -#elif defined(BACKWARD_SYSTEM_WINDOWS) -#else -#if defined(__linux) || defined(__linux__) -#define BACKWARD_SYSTEM_LINUX -#elif defined(__APPLE__) -#define BACKWARD_SYSTEM_DARWIN -#elif defined(_WIN32) -#define BACKWARD_SYSTEM_WINDOWS -#else -#define BACKWARD_SYSTEM_UNKNOWN -#endif -#endif - -#define NOINLINE __attribute__((noinline)) - -#include "Pipe/Memory/STLAllocator.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(BACKWARD_SYSTEM_LINUX) - -// On linux, backtrace can back-trace or "walk" the stack using the following -// libraries: -// -// #define BACKWARD_HAS_UNWIND 1 -// - unwind comes from libgcc, but I saw an equivalent inside clang itself. -// - with unwind, the stacktrace is as accurate as it can possibly be, since -// this is used by the C++ runtine in gcc/clang for stack unwinding on -// exception. -// - normally libgcc is already linked to your program by default. -// -// #define BACKWARD_HAS_LIBUNWIND 1 -// - libunwind provides, in some cases, a more accurate stacktrace as it knows -// to decode signal handler frames and lets us edit the context registers when -// unwinding, allowing stack traces over bad function references. -// -// #define BACKWARD_HAS_BACKTRACE == 1 -// - backtrace seems to be a little bit more portable than libunwind, but on -// linux, it uses unwind anyway, but abstract away a tiny information that is -// sadly really important in order to get perfectly accurate stack traces. -// - backtrace is part of the (e)glib library. -// -// The default is: -// #define BACKWARD_HAS_UNWIND == 1 -// -// Note that only one of the define should be set to 1 at a time. -// -#if BACKWARD_HAS_UNWIND == 1 -#elif BACKWARD_HAS_LIBUNWIND == 1 -#elif BACKWARD_HAS_BACKTRACE == 1 -#else -#undef BACKWARD_HAS_UNWIND -#define BACKWARD_HAS_UNWIND 1 -#undef BACKWARD_HAS_LIBUNWIND -#define BACKWARD_HAS_LIBUNWIND 0 -#undef BACKWARD_HAS_BACKTRACE -#define BACKWARD_HAS_BACKTRACE 0 -#endif - -// On linux, backward can extract detailed information about a stack trace -// using one of the following libraries: -// -// #define BACKWARD_HAS_DW 1 -// - libdw gives you the most juicy details out of your stack traces: -// - object filename -// - function name -// - source filename -// - line and column numbers -// - source code snippet (assuming the file is accessible) -// - variable names (if not optimized out) -// - variable values (not supported by backward-cpp) -// - You need to link with the lib "dw": -// - apt-get install libdw-dev -// - g++/clang++ -ldw ... -// -// #define BACKWARD_HAS_BFD 1 -// - With libbfd, you get a fair amount of details: -// - object filename -// - function name -// - source filename -// - line numbers -// - source code snippet (assuming the file is accessible) -// - You need to link with the lib "bfd": -// - apt-get install binutils-dev -// - g++/clang++ -lbfd ... -// -// #define BACKWARD_HAS_DWARF 1 -// - libdwarf gives you the most juicy details out of your stack traces: -// - object filename -// - function name -// - source filename -// - line and column numbers -// - source code snippet (assuming the file is accessible) -// - variable names (if not optimized out) -// - variable values (not supported by backward-cpp) -// - You need to link with the lib "dwarf": -// - apt-get install libdwarf-dev -// - g++/clang++ -ldwarf ... -// -// #define BACKWARD_HAS_BACKTRACE_SYMBOL 1 -// - backtrace provides minimal details for a stack trace: -// - object filename -// - function name -// - backtrace is part of the (e)glib library. -// -// The default is: -// #define BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -// -// Note that only one of the define should be set to 1 at a time. -// -#if BACKWARD_HAS_DW == 1 -#elif BACKWARD_HAS_BFD == 1 -#elif BACKWARD_HAS_DWARF == 1 -#elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -#else -#undef BACKWARD_HAS_DW -#define BACKWARD_HAS_DW 0 -#undef BACKWARD_HAS_BFD -#define BACKWARD_HAS_BFD 0 -#undef BACKWARD_HAS_DWARF -#define BACKWARD_HAS_DWARF 0 -#undef BACKWARD_HAS_BACKTRACE_SYMBOL -#define BACKWARD_HAS_BACKTRACE_SYMBOL 1 -#endif - -#include -#include -#ifdef __ANDROID__ -// Old Android API levels define _Unwind_Ptr in both link.h and -// unwind.h Rename the one in link.h as we are not going to be using -// it -#define _Unwind_Ptr _Unwind_Ptr_Custom -#include -#undef _Unwind_Ptr -#else -#include -#endif -#if defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || \ - defined(__POWERPC__) -// Linux kernel header required for the struct pt_regs definition -// to access the NIP (Next Instruction Pointer) register value -#include -#endif -#include -#include -#include -#include - -#if BACKWARD_HAS_BFD == 1 -// NOTE: defining PACKAGE{,_VERSION} is required before including -// bfd.h on some platforms, see also: -// https://sourceware.org/bugzilla/show_bug.cgi?id=14243 -#ifndef PACKAGE -#define PACKAGE -#endif -#ifndef PACKAGE_VERSION -#define PACKAGE_VERSION -#endif -#include -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#include -#undef _GNU_SOURCE -#else -#include -#endif -#endif - -#if BACKWARD_HAS_DW == 1 -#include -#include -#include -#endif - -#if BACKWARD_HAS_DWARF == 1 -#include -#include -#include -#include -#include -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#include -#undef _GNU_SOURCE -#else -#include -#endif -#endif - -#if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) -// then we shall rely on backtrace -#include -#endif - -#endif // defined(BACKWARD_SYSTEM_LINUX) - -#if defined(BACKWARD_SYSTEM_DARWIN) -// On Darwin, backtrace can back-trace or "walk" the stack using the following -// libraries: -// -// #define BACKWARD_HAS_UNWIND 1 -// - unwind comes from libgcc, but I saw an equivalent inside clang itself. -// - with unwind, the stacktrace is as accurate as it can possibly be, since -// this is used by the C++ runtine in gcc/clang for stack unwinding on -// exception. -// - normally libgcc is already linked to your program by default. -// -// #define BACKWARD_HAS_LIBUNWIND 1 -// - libunwind comes from clang, which implements an API compatible version. -// - libunwind provides, in some cases, a more accurate stacktrace as it knows -// to decode signal handler frames and lets us edit the context registers when -// unwinding, allowing stack traces over bad function references. -// -// #define BACKWARD_HAS_BACKTRACE == 1 -// - backtrace is available by default, though it does not produce as much -// information as another library might. -// -// The default is: -// #define BACKWARD_HAS_UNWIND == 1 -// -// Note that only one of the define should be set to 1 at a time. -// -#if BACKWARD_HAS_UNWIND == 1 -#elif BACKWARD_HAS_BACKTRACE == 1 -#elif BACKWARD_HAS_LIBUNWIND == 1 -#else -#undef BACKWARD_HAS_UNWIND -#define BACKWARD_HAS_UNWIND 1 -#undef BACKWARD_HAS_BACKTRACE -#define BACKWARD_HAS_BACKTRACE 0 -#undef BACKWARD_HAS_LIBUNWIND -#define BACKWARD_HAS_LIBUNWIND 0 -#endif - -// On Darwin, backward can extract detailed information about a stack trace -// using one of the following libraries: -// -// #define BACKWARD_HAS_BACKTRACE_SYMBOL 1 -// - backtrace provides minimal details for a stack trace: -// - object filename -// - function name -// -// The default is: -// #define BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -// -#if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -#else -#undef BACKWARD_HAS_BACKTRACE_SYMBOL -#define BACKWARD_HAS_BACKTRACE_SYMBOL 1 -#endif - -#include -#include -#include -#include -#include -#include - -#if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) -#include -#endif -#endif // defined(BACKWARD_SYSTEM_DARWIN) - -#if defined(BACKWARD_SYSTEM_WINDOWS) - -#include -#include -#include - -#include - -#ifdef _WIN64 -typedef SSIZE_T ssize_t; -#else -typedef int ssize_t; -#endif - -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -#include - -#include -#include - -#ifndef __clang__ -#undef NOINLINE -#define NOINLINE __declspec(noinline) -#endif - -#ifdef _MSC_VER -#pragma comment(lib, "psapi.lib") -#pragma comment(lib, "dbghelp.lib") -#endif - -// Comment / packing is from stackoverflow: -// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227 -// Some versions of imagehlp.dll lack the proper packing directives themselves -// so we need to do it. -#pragma pack(push, before_imagehlp, 8) -#include -#pragma pack(pop, before_imagehlp) - -// TODO maybe these should be undefined somewhere else? -#undef BACKWARD_HAS_UNWIND -#undef BACKWARD_HAS_BACKTRACE -#if BACKWARD_HAS_PDB_SYMBOL == 1 -#else -#undef BACKWARD_HAS_PDB_SYMBOL -#define BACKWARD_HAS_PDB_SYMBOL 1 -#endif - -#endif - -#if BACKWARD_HAS_UNWIND == 1 - -#include -// while gcc's unwind.h defines something like that: -// extern _Unwind_Ptr _Unwind_GetIP (struct _Unwind_Context *); -// extern _Unwind_Ptr _Unwind_GetIPInfo (struct _Unwind_Context *, int *); -// -// clang's unwind.h defines something like this: -// uintptr_t _Unwind_GetIP(struct _Unwind_Context* __context); -// -// Even if the _Unwind_GetIPInfo can be linked to, it is not declared, worse we -// cannot just redeclare it because clang's unwind.h doesn't define _Unwind_Ptr -// anyway. -// -// Luckily we can play on the fact that the guard macros have a different name: -#ifdef __CLANG_UNWIND_H -// In fact, this function still comes from libgcc (on my different linux boxes, -// clang links against libgcc). -#include -extern "C" uintptr_t _Unwind_GetIPInfo(_Unwind_Context *, int *); -#endif - -#endif // BACKWARD_HAS_UNWIND == 1 - -#if BACKWARD_HAS_LIBUNWIND == 1 -#define UNW_LOCAL_ONLY -#include -#endif // BACKWARD_HAS_LIBUNWIND == 1 - -#ifdef BACKWARD_ATLEAST_CXX11 -#include -#include // for std::swap -namespace backward { -namespace details { -template struct hashtable { - typedef std::unordered_map type; -}; -using std::move; -} // namespace details -} // namespace backward -#else // NOT BACKWARD_ATLEAST_CXX11 -#define nullptr NULL -#define override -#include -namespace backward { -namespace details { -template struct hashtable { - typedef std::map type; -}; -template const T &move(const T &v) { return v; } -template T &move(T &v) { return v; } -} // namespace details -} // namespace backward -#endif // BACKWARD_ATLEAST_CXX11 - -namespace backward { -namespace details { -#if defined(BACKWARD_SYSTEM_WINDOWS) -const char kBackwardPathDelimiter[] = ";"; -#else -const char kBackwardPathDelimiter[] = ":"; -#endif -} // namespace details -} // namespace backward - -namespace backward { - -namespace system_tag { -struct linux_tag; // seems that I cannot call that "linux" because the name -// is already defined... so I am adding _tag everywhere. -struct darwin_tag; -struct windows_tag; -struct unknown_tag; - -#if defined(BACKWARD_SYSTEM_LINUX) -typedef linux_tag current_tag; -#elif defined(BACKWARD_SYSTEM_DARWIN) -typedef darwin_tag current_tag; -#elif defined(BACKWARD_SYSTEM_WINDOWS) -typedef windows_tag current_tag; -#elif defined(BACKWARD_SYSTEM_UNKNOWN) -typedef unknown_tag current_tag; -#else -#error "May I please get my system defines?" -#endif -} // namespace system_tag - -namespace trace_resolver_tag { -#if defined(BACKWARD_SYSTEM_LINUX) -struct libdw; -struct libbfd; -struct libdwarf; -struct backtrace_symbol; - -#if BACKWARD_HAS_DW == 1 -typedef libdw current; -#elif BACKWARD_HAS_BFD == 1 -typedef libbfd current; -#elif BACKWARD_HAS_DWARF == 1 -typedef libdwarf current; -#elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -typedef backtrace_symbol current; -#else -#error "You shall not pass, until you know what you want." -#endif -#elif defined(BACKWARD_SYSTEM_DARWIN) -struct backtrace_symbol; - -#if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -typedef backtrace_symbol current; -#else -#error "You shall not pass, until you know what you want." -#endif -#elif defined(BACKWARD_SYSTEM_WINDOWS) -struct pdb_symbol; -#if BACKWARD_HAS_PDB_SYMBOL == 1 -typedef pdb_symbol current; -#else -#error "You shall not pass, until you know what you want." -#endif -#endif -} // namespace trace_resolver_tag - -namespace details { - -template struct rm_ptr { typedef T type; }; - -template struct rm_ptr { typedef T type; }; - -template struct rm_ptr { typedef const T type; }; - -template struct deleter { - template void operator()(U &ptr) const { (*F)(ptr); } -}; - -template struct default_delete { - void operator()(T &ptr) const { delete ptr; } -}; - -template > -class handle { - struct dummy; - T _val; - bool _empty; - -#ifdef BACKWARD_ATLEAST_CXX11 - handle(const handle &) = delete; - handle &operator=(const handle &) = delete; -#endif - -public: - ~handle() { - if (!_empty) { - Deleter()(_val); - } - } - - explicit handle() : _val(), _empty(true) {} - explicit handle(T val) : _val(val), _empty(false) { - if (!_val) - _empty = true; - } - -#ifdef BACKWARD_ATLEAST_CXX11 - handle(handle &&from) : _empty(true) { swap(from); } - handle &operator=(handle &&from) { - swap(from); - return *this; - } -#else - explicit handle(const handle &from) : _empty(true) { - // some sort of poor man's move semantic. - swap(const_cast(from)); - } - handle &operator=(const handle &from) { - // some sort of poor man's move semantic. - swap(const_cast(from)); - return *this; - } -#endif - - void reset(T new_val) { - handle tmp(new_val); - swap(tmp); - } - - void update(T new_val) { - _val = new_val; - _empty = !static_cast(new_val); - } - - operator const dummy *() const { - if (_empty) { - return nullptr; - } - return reinterpret_cast(_val); - } - T get() { return _val; } - T release() { - _empty = true; - return _val; - } - void swap(handle &b) { - using std::swap; - swap(b._val, _val); // can throw, we are safe here. - swap(b._empty, _empty); // should not throw: if you cannot swap two - // bools without throwing... It's a lost cause anyway! - } - - T &operator->() { return _val; } - const T &operator->() const { return _val; } - - typedef typename rm_ptr::type &ref_t; - typedef const typename rm_ptr::type &const_ref_t; - ref_t operator*() { return *_val; } - const_ref_t operator*() const { return *_val; } - ref_t operator[](size_t idx) { return _val[idx]; } - - // Watch out, we've got a badass over here - T *operator&() { - _empty = false; - return &_val; - } -}; - -// Default demangler implementation (do nothing). -template struct demangler_impl { - static std::string demangle(const char *funcname) { return funcname; } -}; - -#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) - -template <> struct demangler_impl { - demangler_impl() : _demangle_buffer_length(0) {} - - std::string demangle(const char *funcname) { - using namespace details; - char *result = abi::__cxa_demangle(funcname, _demangle_buffer.get(), - &_demangle_buffer_length, nullptr); - if (result) { - _demangle_buffer.update(result); - return result; - } - return funcname; - } - -private: - details::handle _demangle_buffer; - size_t _demangle_buffer_length; -}; - -#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN - -struct demangler : public demangler_impl {}; - -// Split a string on the platform's PATH delimiter. Example: if delimiter -// is ":" then: -// "" --> [] -// ":" --> ["",""] -// "::" --> ["","",""] -// "/a/b/c" --> ["/a/b/c"] -// "/a/b/c:/d/e/f" --> ["/a/b/c","/d/e/f"] -// etc. -inline std::vector split_source_prefixes(const std::string &s) { - std::vector out; - size_t last = 0; - size_t next = 0; - size_t delimiter_size = sizeof(kBackwardPathDelimiter) - 1; - while ((next = s.find(kBackwardPathDelimiter, last)) != std::string::npos) { - out.push_back(s.substr(last, next - last)); - last = next + delimiter_size; - } - if (last <= s.length()) { - out.push_back(s.substr(last)); - } - return out; -} - -} // namespace details - -/*************** A TRACE ***************/ - -struct Trace { - void *addr; - size_t idx; - - Trace() : addr(nullptr), idx(0) {} - - explicit Trace(void *_addr, size_t _idx) : addr(_addr), idx(_idx) {} -}; - -struct ResolvedTrace : public Trace { - - struct SourceLoc { - std::string function; - std::string filename; - unsigned line; - unsigned col; - - SourceLoc() : line(0), col(0) {} - - bool operator==(const SourceLoc &b) const { - return function == b.function && filename == b.filename && - line == b.line && col == b.col; - } - - bool operator!=(const SourceLoc &b) const { return !(*this == b); } - }; - - // In which binary object this trace is located. - std::string object_filename; - - // The function in the object that contain the trace. This is not the same - // as source.function which can be an function inlined in object_function. - std::string object_function; - - // The source location of this trace. It is possible for filename to be - // empty and for line/col to be invalid (value 0) if this information - // couldn't be deduced, for example if there is no debug information in the - // binary object. - SourceLoc source; - - // An optionals list of "inliners". All the successive sources location - // from where the source location of the trace (the attribute right above) - // is inlined. It is especially useful when you compiled with optimization. - typedef std::vector source_locs_t; - source_locs_t inliners; - - ResolvedTrace() : Trace() {} - ResolvedTrace(const Trace &mini_trace) : Trace(mini_trace) {} -}; - -/*************** STACK TRACE ***************/ - -// default implemention. -template -class StackTraceImpl { -public: - size_t size() const { return 0; } - Trace operator[](size_t) const { return Trace(); } - size_t load_here(size_t = 0) { return 0; } - size_t load_from(void *, size_t = 0, void * = nullptr, void * = nullptr) { - return 0; - } - size_t thread_id() const { return 0; } - void skip_n_firsts(size_t) {} -}; - -class StackTraceImplBase { -public: - StackTraceImplBase() - : _thread_id(0), _skip(0), _context(nullptr), _error_addr(nullptr) {} - - size_t thread_id() const { return _thread_id; } - - void skip_n_firsts(size_t n) { _skip = n; } - -protected: - void load_thread_info() { -#ifdef BACKWARD_SYSTEM_LINUX -#ifndef __ANDROID__ - _thread_id = static_cast(syscall(SYS_gettid)); -#else - _thread_id = static_cast(gettid()); -#endif - if (_thread_id == static_cast(getpid())) { - // If the thread is the main one, let's hide that. - // I like to keep little secret sometimes. - _thread_id = 0; - } -#elif defined(BACKWARD_SYSTEM_DARWIN) - _thread_id = reinterpret_cast(pthread_self()); - if (pthread_main_np() == 1) { - // If the thread is the main one, let's hide that. - _thread_id = 0; - } -#endif - } - - void set_context(void *context) { _context = context; } - void *context() const { return _context; } - - void set_error_addr(void *error_addr) { _error_addr = error_addr; } - void *error_addr() const { return _error_addr; } - - size_t skip_n_firsts() const { return _skip; } - -private: - size_t _thread_id; - size_t _skip; - void *_context; - void *_error_addr; -}; - - -class StackTraceImplHolder : public StackTraceImplBase { -public: - StackTraceImplHolder(p::Arena& arena) : _stacktrace{p::STLAllocator(arena)} {} - - size_t size() const { - return (_stacktrace.size() >= skip_n_firsts()) - ? _stacktrace.size() - skip_n_firsts() - : 0; - } - Trace operator[](size_t idx) const { - if (idx >= size()) { - return Trace(); - } - return Trace(_stacktrace[idx + skip_n_firsts()], idx); - } - void *const *begin() const { - if (size()) { - return &_stacktrace[skip_n_firsts()]; - } - return nullptr; - } - -protected: - std::vector> _stacktrace; -}; - -#if BACKWARD_HAS_UNWIND == 1 - -namespace details { - -template class Unwinder { -public: - size_t operator()(F &f, size_t depth) { - _f = &f; - _index = -1; - _depth = depth; - _Unwind_Backtrace(&this->backtrace_trampoline, this); - return static_cast(_index); - } - -private: - F *_f; - ssize_t _index; - size_t _depth; - - static _Unwind_Reason_Code backtrace_trampoline(_Unwind_Context *ctx, - void *self) { - return (static_cast(self))->backtrace(ctx); - } - - _Unwind_Reason_Code backtrace(_Unwind_Context *ctx) { - if (_index >= 0 && static_cast(_index) >= _depth) - return _URC_END_OF_STACK; - - int ip_before_instruction = 0; - uintptr_t ip = _Unwind_GetIPInfo(ctx, &ip_before_instruction); - - if (!ip_before_instruction) { - // calculating 0-1 for unsigned, looks like a possible bug to sanitiziers, - // so let's do it explicitly: - if (ip == 0) { - ip = std::numeric_limits::max(); // set it to 0xffff... (as - // from casting 0-1) - } else { - ip -= 1; // else just normally decrement it (no overflow/underflow will - // happen) - } - } - - if (_index >= 0) { // ignore first frame. - (*_f)(static_cast(_index), reinterpret_cast(ip)); - } - _index += 1; - return _URC_NO_REASON; - } -}; - -template size_t unwind(F f, size_t depth) { - Unwinder unwinder; - return unwinder(f, depth); -} - -} // namespace details - -template <> -class StackTraceImpl : public StackTraceImplHolder { - using Super = StackTraceImplHolder; -public: - using StackTraceImplHolder::StackTraceImplHolder; - NOINLINE - size_t load_here(size_t depth = 32, void *context = nullptr, - void *error_addr = nullptr) { - Super::load_thread_info(); - Super::set_context(context); - Super::set_error_addr(error_addr); - if (depth == 0) { - return 0; - } - Super::_stacktrace.resize(depth); - size_t trace_cnt = details::unwind(callback(*this), depth); - Super::_stacktrace.resize(trace_cnt); - Super::skip_n_firsts(0); - return Super::size(); - } - size_t load_from(void *addr, size_t depth = 32, void *context = nullptr, - void *error_addr = nullptr) { - load_here(depth + 8, context, error_addr); - - for (size_t i = 0; i < Super::_stacktrace.size(); ++i) { - if (Super::_stacktrace[i] == addr) { - Super::skip_n_firsts(i); - break; - } - } - - Super::_stacktrace.resize(std::min(Super::_stacktrace.size(), Super::skip_n_firsts() + depth)); - return Super::size(); - } - -private: - struct callback { - StackTraceImpl &self; - callback(StackTraceImpl &_self) : self(_self) {} - - void operator()(size_t idx, void *addr) { self._stacktrace[idx] = addr; } - }; -}; - -#elif BACKWARD_HAS_LIBUNWIND == 1 - -template <> -class StackTraceImpl : public StackTraceImplHolder { - using Super = StackTraceImplHolder; -public: - using StackTraceImplHolder::StackTraceImplHolder; - __attribute__((noinline)) size_t load_here(size_t depth = 32, - void *_context = nullptr, - void *_error_addr = nullptr) { - Super::set_context(_context); - Super::set_error_addr(_error_addr); - Super::load_thread_info(); - if (depth == 0) { - return 0; - } - Super::_stacktrace.resize(depth + 1); - - int result = 0; - - unw_context_t ctx; - size_t index = 0; - - // Add the tail call. If the Instruction Pointer is the crash address it - // means we got a bad function pointer dereference, so we "unwind" the - // bad pointer manually by using the return address pointed to by the - // Stack Pointer as the Instruction Pointer and letting libunwind do - // the rest - - if (context()) { - ucontext_t *uctx = reinterpret_cast(context()); -#ifdef REG_RIP // x86_64 - if (uctx->uc_mcontext.gregs[REG_RIP] == - reinterpret_cast(error_addr())) { - uctx->uc_mcontext.gregs[REG_RIP] = - *reinterpret_cast(uctx->uc_mcontext.gregs[REG_RSP]); - } - _stacktrace[index] = - reinterpret_cast(uctx->uc_mcontext.gregs[REG_RIP]); - ++index; - ctx = *reinterpret_cast(uctx); -#elif defined(REG_EIP) // x86_32 - if (uctx->uc_mcontext.gregs[REG_EIP] == - reinterpret_cast(error_addr())) { - uctx->uc_mcontext.gregs[REG_EIP] = - *reinterpret_cast(uctx->uc_mcontext.gregs[REG_ESP]); - } - Super::_stacktrace[index] = - reinterpret_cast(uctx->uc_mcontext.gregs[REG_EIP]); - ++index; - ctx = *reinterpret_cast(uctx); -#elif defined(__arm__) - // libunwind uses its own context type for ARM unwinding. - // Copy the registers from the signal handler's context so we can - // unwind - unw_getcontext(&ctx); - ctx.regs[UNW_ARM_R0] = uctx->uc_mcontext.arm_r0; - ctx.regs[UNW_ARM_R1] = uctx->uc_mcontext.arm_r1; - ctx.regs[UNW_ARM_R2] = uctx->uc_mcontext.arm_r2; - ctx.regs[UNW_ARM_R3] = uctx->uc_mcontext.arm_r3; - ctx.regs[UNW_ARM_R4] = uctx->uc_mcontext.arm_r4; - ctx.regs[UNW_ARM_R5] = uctx->uc_mcontext.arm_r5; - ctx.regs[UNW_ARM_R6] = uctx->uc_mcontext.arm_r6; - ctx.regs[UNW_ARM_R7] = uctx->uc_mcontext.arm_r7; - ctx.regs[UNW_ARM_R8] = uctx->uc_mcontext.arm_r8; - ctx.regs[UNW_ARM_R9] = uctx->uc_mcontext.arm_r9; - ctx.regs[UNW_ARM_R10] = uctx->uc_mcontext.arm_r10; - ctx.regs[UNW_ARM_R11] = uctx->uc_mcontext.arm_fp; - ctx.regs[UNW_ARM_R12] = uctx->uc_mcontext.arm_ip; - ctx.regs[UNW_ARM_R13] = uctx->uc_mcontext.arm_sp; - ctx.regs[UNW_ARM_R14] = uctx->uc_mcontext.arm_lr; - ctx.regs[UNW_ARM_R15] = uctx->uc_mcontext.arm_pc; - - // If we have crashed in the PC use the LR instead, as this was - // a bad function dereference - if (reinterpret_cast(error_addr()) == - uctx->uc_mcontext.arm_pc) { - ctx.regs[UNW_ARM_R15] = - uctx->uc_mcontext.arm_lr - sizeof(unsigned long); - } - Super::_stacktrace[index] = reinterpret_cast(ctx.regs[UNW_ARM_R15]); - ++index; -#elif defined(__APPLE__) && defined(__x86_64__) - unw_getcontext(&ctx); - // OS X's implementation of libunwind uses its own context object - // so we need to convert the passed context to libunwind's format - // (information about the data layout taken from unw_getcontext.s - // in Apple's libunwind source - ctx.data[0] = uctx->uc_mcontext->__ss.__rax; - ctx.data[1] = uctx->uc_mcontext->__ss.__rbx; - ctx.data[2] = uctx->uc_mcontext->__ss.__rcx; - ctx.data[3] = uctx->uc_mcontext->__ss.__rdx; - ctx.data[4] = uctx->uc_mcontext->__ss.__rdi; - ctx.data[5] = uctx->uc_mcontext->__ss.__rsi; - ctx.data[6] = uctx->uc_mcontext->__ss.__rbp; - ctx.data[7] = uctx->uc_mcontext->__ss.__rsp; - ctx.data[8] = uctx->uc_mcontext->__ss.__r8; - ctx.data[9] = uctx->uc_mcontext->__ss.__r9; - ctx.data[10] = uctx->uc_mcontext->__ss.__r10; - ctx.data[11] = uctx->uc_mcontext->__ss.__r11; - ctx.data[12] = uctx->uc_mcontext->__ss.__r12; - ctx.data[13] = uctx->uc_mcontext->__ss.__r13; - ctx.data[14] = uctx->uc_mcontext->__ss.__r14; - ctx.data[15] = uctx->uc_mcontext->__ss.__r15; - ctx.data[16] = uctx->uc_mcontext->__ss.__rip; - - // If the IP is the same as the crash address we have a bad function - // dereference The caller's address is pointed to by %rsp, so we - // dereference that value and set it to be the next frame's IP. - if (uctx->uc_mcontext->__ss.__rip == - reinterpret_cast<__uint64_t>(error_addr())) { - ctx.data[16] = - *reinterpret_cast<__uint64_t *>(uctx->uc_mcontext->__ss.__rsp); - } - Super::_stacktrace[index] = reinterpret_cast(ctx.data[16]); - ++index; -#elif defined(__APPLE__) - unw_getcontext(&ctx) - // TODO: Convert the ucontext_t to libunwind's unw_context_t like - // we do in 64 bits - if (ctx.uc_mcontext->__ss.__eip == - reinterpret_cast(error_addr())) { - ctx.uc_mcontext->__ss.__eip = ctx.uc_mcontext->__ss.__esp; - } - Super::_stacktrace[index] = - reinterpret_cast(ctx.uc_mcontext->__ss.__eip); - ++index; -#endif - } - - unw_cursor_t cursor; - if (context()) { -#if defined(UNW_INIT_SIGNAL_FRAME) - result = unw_init_local2(&cursor, &ctx, UNW_INIT_SIGNAL_FRAME); -#else - result = unw_init_local(&cursor, &ctx); -#endif - } else { - unw_getcontext(&ctx); - ; - result = unw_init_local(&cursor, &ctx); - } - - if (result != 0) - return 1; - - unw_word_t ip = 0; - - while (index <= depth && unw_step(&cursor) > 0) { - result = unw_get_reg(&cursor, UNW_REG_IP, &ip); - if (result == 0) { - Super::_stacktrace[index] = reinterpret_cast(--ip); - ++index; - } - } - --index; - - Super::_stacktrace.resize(index + 1); - Super::skip_n_firsts(0); - return Super::size(); - } - - size_t load_from(void *addr, size_t depth = 32, void *context = nullptr, - void *error_addr = nullptr) { - load_here(depth + 8, context, error_addr); - - for (size_t i = 0; i < Super::_stacktrace.size(); ++i) { - if (Super::_stacktrace[i] == addr) { - Super::skip_n_firsts(i); - Super::_stacktrace[i] = (void *)((uintptr_t)Super::_stacktrace[i]); - break; - } - } - - Super::_stacktrace.resize(std::min(Super::_stacktrace.size(), Super::skip_n_firsts() + depth)); - return Super::size(); - } -}; - -#elif defined(BACKWARD_HAS_BACKTRACE) - -template <> -class StackTraceImpl : public StackTraceImplHolder { - using Super = StackTraceImplHolder; -public: - using StackTraceImplHolder::StackTraceImplHolder; - NOINLINE - size_t load_here(size_t depth = 32, void *context = nullptr, - void *error_addr = nullptr) { - Super::set_context(context); - Super::set_error_addr(error_addr); - Super::load_thread_info(); - if (depth == 0) { - return 0; - } - Super::_stacktrace.resize(depth + 1); - size_t trace_cnt = backtrace(&Super::_stacktrace[0], Super::_stacktrace.size()); - Super::_stacktrace.resize(trace_cnt); - Super::skip_n_firsts(1); - return Super::size(); - } - - size_t load_from(void *addr, size_t depth = 32, void *context = nullptr, - void *error_addr = nullptr) { - load_here(depth + 8, context, error_addr); - - for (size_t i = 0; i < Super::_stacktrace.size(); ++i) { - if (Super::_stacktrace[i] == addr) { - Super::skip_n_firsts(i); - Super::_stacktrace[i] = (void *)((uintptr_t)Super::_stacktrace[i] + 1); - break; - } - } - - Super::_stacktrace.resize(std::min(Super::_stacktrace.size(), Super::skip_n_firsts() + depth)); - return Super::size(); - } -}; - -#elif defined(BACKWARD_SYSTEM_WINDOWS) - -template <> -class StackTraceImpl : public StackTraceImplHolder { - using Super = StackTraceImplHolder; -public: - using StackTraceImplHolder::StackTraceImplHolder; - // We have to load the machine type from the image info - // So we first initialize the resolver, and it tells us this info - void set_machine_type(DWORD machine_type) { machine_type_ = machine_type; } - void set_context(CONTEXT *ctx) { ctx_ = ctx; } - void set_thread_handle(HANDLE handle) { thd_ = handle; } - - NOINLINE - size_t load_here(size_t depth = 32, void *context = nullptr, - void *error_addr = nullptr) { - Super::set_context(static_cast(context)); - Super::set_error_addr(error_addr); - CONTEXT localCtx; // used when no context is provided - - if (depth == 0) { - return 0; - } - - if (!ctx_) { - ctx_ = &localCtx; - RtlCaptureContext(ctx_); - } - - if (!thd_) { - thd_ = GetCurrentThread(); - } - - HANDLE process = GetCurrentProcess(); - - STACKFRAME64 s; - memset(&s, 0, sizeof(STACKFRAME64)); - - // TODO: 32 bit context capture - s.AddrStack.Mode = AddrModeFlat; - s.AddrFrame.Mode = AddrModeFlat; - s.AddrPC.Mode = AddrModeFlat; -#ifdef _M_X64 - s.AddrPC.Offset = ctx_->Rip; - s.AddrStack.Offset = ctx_->Rsp; - s.AddrFrame.Offset = ctx_->Rbp; -#else - s.AddrPC.Offset = ctx_->Eip; - s.AddrStack.Offset = ctx_->Esp; - s.AddrFrame.Offset = ctx_->Ebp; -#endif - - if (!machine_type_) { -#ifdef _M_X64 - machine_type_ = IMAGE_FILE_MACHINE_AMD64; -#else - machine_type_ = IMAGE_FILE_MACHINE_I386; -#endif - } - - for (;;) { - // NOTE: this only works if PDBs are already loaded! - SetLastError(0); - if (!StackWalk64(machine_type_, process, thd_, &s, ctx_, NULL, - SymFunctionTableAccess64, SymGetModuleBase64, NULL)) - break; - - if (s.AddrReturn.Offset == 0) - break; - - Super::_stacktrace.push_back(reinterpret_cast(s.AddrPC.Offset)); - - if (Super::size() >= depth) - break; - } - - return Super::size(); - } - - size_t load_from(void *addr, size_t depth = 32, void *context = nullptr, - void *error_addr = nullptr) { - load_here(depth + 8, context, error_addr); - - for (size_t i = 0; i < Super::_stacktrace.size(); ++i) { - if (Super::_stacktrace[i] == addr) { - Super::skip_n_firsts(i); - break; - } - } - - Super::_stacktrace.resize(std::min(Super::_stacktrace.size(), Super::skip_n_firsts() + depth)); - return Super::size(); - } - -private: - DWORD machine_type_ = 0; - HANDLE thd_ = 0; - CONTEXT *ctx_ = nullptr; -}; - -#endif - -class StackTrace : public StackTraceImpl -{ - using StackTraceImpl::StackTraceImpl; -}; - -/*************** TRACE RESOLVER ***************/ - -class TraceResolverImplBase { -public: - virtual ~TraceResolverImplBase() {} - - virtual void load_addresses(void *const*addresses, int address_count) { - (void)addresses; - (void)address_count; - } - - template void load_stacktrace(ST &st) { - load_addresses(st.begin(), static_cast(st.size())); - } - - virtual ResolvedTrace resolve(ResolvedTrace t) { return t; } - -protected: - std::string demangle(const char *funcname) { - return _demangler.demangle(funcname); - } - -private: - details::demangler _demangler; -}; - -template class TraceResolverImpl; - -#ifdef BACKWARD_SYSTEM_UNKNOWN - -template <> class TraceResolverImpl - : public TraceResolverImplBase {}; - -#endif - -#ifdef BACKWARD_SYSTEM_LINUX - -class TraceResolverLinuxBase : public TraceResolverImplBase { -public: - TraceResolverLinuxBase() - : argv0_(get_argv0()), exec_path_(read_symlink("/proc/self/exe")) {} - std::string resolve_exec_path(Dl_info &symbol_info) const { - // mutates symbol_info.dli_fname to be filename to open and returns filename - // to display - if (symbol_info.dli_fname == argv0_) { - // dladdr returns argv[0] in dli_fname for symbols contained in - // the main executable, which is not a valid path if the - // executable was found by a search of the PATH environment - // variable; In that case, we actually open /proc/self/exe, which - // is always the actual executable (even if it was deleted/replaced!) - // but display the path that /proc/self/exe links to. - // However, this right away reduces probability of successful symbol - // resolution, because libbfd may try to find *.debug files in the - // same dir, in case symbols are stripped. As a result, it may try - // to find a file /proc/self/.debug, which obviously does - // not exist. /proc/self/exe is a last resort. First load attempt - // should go for the original executable file path. - symbol_info.dli_fname = "/proc/self/exe"; - return exec_path_; - } else { - return symbol_info.dli_fname; - } - } - -private: - std::string argv0_; - std::string exec_path_; - - static std::string get_argv0() { - std::string argv0; - std::ifstream ifs("/proc/self/cmdline"); - std::getline(ifs, argv0, '\0'); - return argv0; - } - - static std::string read_symlink(std::string const &symlink_path) { - std::string path; - path.resize(100); - - while (true) { - ssize_t len = - ::readlink(symlink_path.c_str(), &*path.begin(), path.size()); - if (len < 0) { - return ""; - } - if (static_cast(len) == path.size()) { - path.resize(path.size() * 2); - } else { - path.resize(static_cast(len)); - break; - } - } - - return path; - } -}; - -template class TraceResolverLinuxImpl; - -#if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 - -template <> -class TraceResolverLinuxImpl - : public TraceResolverLinuxBase { -public: - void load_addresses(void *const*addresses, int address_count) override { - if (address_count == 0) { - return; - } - _symbols.reset(backtrace_symbols(addresses, address_count)); - } - - ResolvedTrace resolve(ResolvedTrace trace) override { - char *filename = _symbols[trace.idx]; - char *funcname = filename; - while (*funcname && *funcname != '(') { - funcname += 1; - } - trace.object_filename.assign(filename, - funcname); // ok even if funcname is the ending - // \0 (then we assign entire string) - - if (*funcname) { // if it's not end of string (e.g. from last frame ip==0) - funcname += 1; - char *funcname_end = funcname; - while (*funcname_end && *funcname_end != ')' && *funcname_end != '+') { - funcname_end += 1; - } - *funcname_end = '\0'; - trace.object_function = this->demangle(funcname); - trace.source.function = trace.object_function; // we cannot do better. - } - return trace; - } - -private: - details::handle _symbols; -}; - -#endif // BACKWARD_HAS_BACKTRACE_SYMBOL == 1 - -#if BACKWARD_HAS_BFD == 1 - -template <> -class TraceResolverLinuxImpl - : public TraceResolverLinuxBase { -public: - TraceResolverLinuxImpl() : _bfd_loaded(false) {} - - ResolvedTrace resolve(ResolvedTrace trace) override { - Dl_info symbol_info; - - // trace.addr is a virtual address in memory pointing to some code. - // Let's try to find from which loaded object it comes from. - // The loaded object can be yourself btw. - if (!dladdr(trace.addr, &symbol_info)) { - return trace; // dat broken trace... - } - - // Now we get in symbol_info: - // .dli_fname: - // pathname of the shared object that contains the address. - // .dli_fbase: - // where the object is loaded in memory. - // .dli_sname: - // the name of the nearest symbol to trace.addr, we expect a - // function name. - // .dli_saddr: - // the exact address corresponding to .dli_sname. - - if (symbol_info.dli_sname) { - trace.object_function = demangle(symbol_info.dli_sname); - } - - if (!symbol_info.dli_fname) { - return trace; - } - - trace.object_filename = resolve_exec_path(symbol_info); - bfd_fileobject *fobj; - // Before rushing to resolution need to ensure the executable - // file still can be used. For that compare inode numbers of - // what is stored by the executable's file path, and in the - // dli_fname, which not necessarily equals to the executable. - // It can be a shared library, or /proc/self/exe, and in the - // latter case has drawbacks. See the exec path resolution for - // details. In short - the dli object should be used only as - // the last resort. - // If inode numbers are equal, it is known dli_fname and the - // executable file are the same. This is guaranteed by Linux, - // because if the executable file is changed/deleted, it will - // be done in a new inode. The old file will be preserved in - // /proc/self/exe, and may even have inode 0. The latter can - // happen if the inode was actually reused, and the file was - // kept only in the main memory. - // - struct stat obj_stat; - struct stat dli_stat; - if (stat(trace.object_filename.c_str(), &obj_stat) == 0 && - stat(symbol_info.dli_fname, &dli_stat) == 0 && - obj_stat.st_ino == dli_stat.st_ino) { - // The executable file, and the shared object containing the - // address are the same file. Safe to use the original path. - // this is preferable. Libbfd will search for stripped debug - // symbols in the same directory. - fobj = load_object_with_bfd(trace.object_filename); - } else{ - // The original object file was *deleted*! The only hope is - // that the debug symbols are either inside the shared - // object file, or are in the same directory, and this is - // not /proc/self/exe. - fobj = nullptr; - } - if (fobj == nullptr || !fobj->handle) { - fobj = load_object_with_bfd(symbol_info.dli_fname); - if (!fobj->handle) { - return trace; - } - } - - find_sym_result *details_selected; // to be filled. - - // trace.addr is the next instruction to be executed after returning - // from the nested stack frame. In C++ this usually relate to the next - // statement right after the function call that leaded to a new stack - // frame. This is not usually what you want to see when printing out a - // stacktrace... - find_sym_result details_call_site = - find_symbol_details(fobj, trace.addr, symbol_info.dli_fbase); - details_selected = &details_call_site; - -#if BACKWARD_HAS_UNWIND == 0 - // ...this is why we also try to resolve the symbol that is right - // before the return address. If we are lucky enough, we will get the - // line of the function that was called. But if the code is optimized, - // we might get something absolutely not related since the compiler - // can reschedule the return address with inline functions and - // tail-call optimisation (among other things that I don't even know - // or cannot even dream about with my tiny limited brain). - find_sym_result details_adjusted_call_site = find_symbol_details( - fobj, (void *)(uintptr_t(trace.addr) - 1), symbol_info.dli_fbase); - - // In debug mode, we should always get the right thing(TM). - if (details_call_site.found && details_adjusted_call_site.found) { - // Ok, we assume that details_adjusted_call_site is a better estimation. - details_selected = &details_adjusted_call_site; - trace.addr = (void *)(uintptr_t(trace.addr) - 1); - } - - if (details_selected == &details_call_site && details_call_site.found) { - // we have to re-resolve the symbol in order to reset some - // internal state in BFD... so we can call backtrace_inliners - // thereafter... - details_call_site = - find_symbol_details(fobj, trace.addr, symbol_info.dli_fbase); - } -#endif // BACKWARD_HAS_UNWIND - - if (details_selected->found) { - if (details_selected->filename) { - trace.source.filename = details_selected->filename; - } - trace.source.line = details_selected->line; - - if (details_selected->funcname) { - // this time we get the name of the function where the code is - // located, instead of the function were the address is - // located. In short, if the code was inlined, we get the - // function correspoding to the code. Else we already got in - // trace.function. - trace.source.function = demangle(details_selected->funcname); - - if (!symbol_info.dli_sname) { - // for the case dladdr failed to find the symbol name of - // the function, we might as well try to put something - // here. - trace.object_function = trace.source.function; - } - } - - // Maybe the source of the trace got inlined inside the function - // (trace.source.function). Let's see if we can get all the inlined - // calls along the way up to the initial call site. - trace.inliners = backtrace_inliners(fobj, *details_selected); - -#if 0 - if (trace.inliners.size() == 0) { - // Maybe the trace was not inlined... or maybe it was and we - // are lacking the debug information. Let's try to make the - // world better and see if we can get the line number of the - // function (trace.source.function) now. - // - // We will get the location of where the function start (to be - // exact: the first instruction that really start the - // function), not where the name of the function is defined. - // This can be quite far away from the name of the function - // btw. - // - // If the source of the function is the same as the source of - // the trace, we cannot say if the trace was really inlined or - // not. However, if the filename of the source is different - // between the function and the trace... we can declare it as - // an inliner. This is not 100% accurate, but better than - // nothing. - - if (symbol_info.dli_saddr) { - find_sym_result details = find_symbol_details(fobj, - symbol_info.dli_saddr, - symbol_info.dli_fbase); - - if (details.found) { - ResolvedTrace::SourceLoc diy_inliner; - diy_inliner.line = details.line; - if (details.filename) { - diy_inliner.filename = details.filename; - } - if (details.funcname) { - diy_inliner.function = demangle(details.funcname); - } else { - diy_inliner.function = trace.source.function; - } - if (diy_inliner != trace.source) { - trace.inliners.push_back(diy_inliner); - } - } - } - } -#endif - } - - return trace; - } - -private: - bool _bfd_loaded; - - typedef details::handle > - bfd_handle_t; - - typedef details::handle bfd_symtab_t; - - struct bfd_fileobject { - bfd_handle_t handle; - bfd_vma base_addr; - bfd_symtab_t symtab; - bfd_symtab_t dynamic_symtab; - }; - - typedef details::hashtable::type fobj_bfd_map_t; - fobj_bfd_map_t _fobj_bfd_map; - - bfd_fileobject *load_object_with_bfd(const std::string &filename_object) { - using namespace details; - - if (!_bfd_loaded) { - using namespace details; - bfd_init(); - _bfd_loaded = true; - } - - fobj_bfd_map_t::iterator it = _fobj_bfd_map.find(filename_object); - if (it != _fobj_bfd_map.end()) { - return &it->second; - } - - // this new object is empty for now. - bfd_fileobject *r = &_fobj_bfd_map[filename_object]; - - // we do the work temporary in this one; - bfd_handle_t bfd_handle; - - int fd = open(filename_object.c_str(), O_RDONLY); - bfd_handle.reset(bfd_fdopenr(filename_object.c_str(), "default", fd)); - if (!bfd_handle) { - close(fd); - return r; - } - - if (!bfd_check_format(bfd_handle.get(), bfd_object)) { - return r; // not an object? You lose. - } - - if ((bfd_get_file_flags(bfd_handle.get()) & HAS_SYMS) == 0) { - return r; // that's what happen when you forget to compile in debug. - } - - ssize_t symtab_storage_size = bfd_get_symtab_upper_bound(bfd_handle.get()); - - ssize_t dyn_symtab_storage_size = - bfd_get_dynamic_symtab_upper_bound(bfd_handle.get()); - - if (symtab_storage_size <= 0 && dyn_symtab_storage_size <= 0) { - return r; // weird, is the file is corrupted? - } - - bfd_symtab_t symtab, dynamic_symtab; - ssize_t symcount = 0, dyn_symcount = 0; - - if (symtab_storage_size > 0) { - symtab.reset(static_cast( - malloc(static_cast(symtab_storage_size)))); - symcount = bfd_canonicalize_symtab(bfd_handle.get(), symtab.get()); - } - - if (dyn_symtab_storage_size > 0) { - dynamic_symtab.reset(static_cast( - malloc(static_cast(dyn_symtab_storage_size)))); - dyn_symcount = bfd_canonicalize_dynamic_symtab(bfd_handle.get(), - dynamic_symtab.get()); - } - - if (symcount <= 0 && dyn_symcount <= 0) { - return r; // damned, that's a stripped file that you got there! - } - - r->handle = move(bfd_handle); - r->symtab = move(symtab); - r->dynamic_symtab = move(dynamic_symtab); - return r; - } - - struct find_sym_result { - bool found; - const char *filename; - const char *funcname; - unsigned int line; - }; - - struct find_sym_context { - TraceResolverLinuxImpl *self; - bfd_fileobject *fobj; - void *addr; - void *base_addr; - find_sym_result result; - }; - - find_sym_result find_symbol_details(bfd_fileobject *fobj, void *addr, - void *base_addr) { - find_sym_context context; - context.self = this; - context.fobj = fobj; - context.addr = addr; - context.base_addr = base_addr; - context.result.found = false; - bfd_map_over_sections(fobj->handle.get(), &find_in_section_trampoline, - static_cast(&context)); - return context.result; - } - - static void find_in_section_trampoline(bfd *, asection *section, void *data) { - find_sym_context *context = static_cast(data); - context->self->find_in_section( - reinterpret_cast(context->addr), - reinterpret_cast(context->base_addr), context->fobj, section, - context->result); - } - - void find_in_section(bfd_vma addr, bfd_vma base_addr, bfd_fileobject *fobj, - asection *section, find_sym_result &result) { - if (result.found) - return; - -#ifdef bfd_get_section_flags - if ((bfd_get_section_flags(fobj->handle.get(), section) & SEC_ALLOC) == 0) -#else - if ((bfd_section_flags(section) & SEC_ALLOC) == 0) -#endif - return; // a debug section is never loaded automatically. - -#ifdef bfd_get_section_vma - bfd_vma sec_addr = bfd_get_section_vma(fobj->handle.get(), section); -#else - bfd_vma sec_addr = bfd_section_vma(section); -#endif -#ifdef bfd_get_section_size - bfd_size_type size = bfd_get_section_size(section); -#else - bfd_size_type size = bfd_section_size(section); -#endif - - // are we in the boundaries of the section? - if (addr < sec_addr || addr >= sec_addr + size) { - addr -= base_addr; // oups, a relocated object, lets try again... - if (addr < sec_addr || addr >= sec_addr + size) { - return; - } - } - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" -#endif - if (!result.found && fobj->symtab) { - result.found = bfd_find_nearest_line( - fobj->handle.get(), section, fobj->symtab.get(), addr - sec_addr, - &result.filename, &result.funcname, &result.line); - } - - if (!result.found && fobj->dynamic_symtab) { - result.found = bfd_find_nearest_line( - fobj->handle.get(), section, fobj->dynamic_symtab.get(), - addr - sec_addr, &result.filename, &result.funcname, &result.line); - } -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - } - - ResolvedTrace::source_locs_t - backtrace_inliners(bfd_fileobject *fobj, find_sym_result previous_result) { - // This function can be called ONLY after a SUCCESSFUL call to - // find_symbol_details. The state is global to the bfd_handle. - ResolvedTrace::source_locs_t results; - while (previous_result.found) { - find_sym_result result; - result.found = bfd_find_inliner_info(fobj->handle.get(), &result.filename, - &result.funcname, &result.line); - - if (result - .found) /* and not ( - cstrings_eq(previous_result.filename, - result.filename) and - cstrings_eq(previous_result.funcname, result.funcname) - and result.line == previous_result.line - )) */ - { - ResolvedTrace::SourceLoc src_loc; - src_loc.line = result.line; - if (result.filename) { - src_loc.filename = result.filename; - } - if (result.funcname) { - src_loc.function = demangle(result.funcname); - } - results.push_back(src_loc); - } - previous_result = result; - } - return results; - } - - bool cstrings_eq(const char *a, const char *b) { - if (!a || !b) { - return false; - } - return strcmp(a, b) == 0; - } -}; -#endif // BACKWARD_HAS_BFD == 1 - -#if BACKWARD_HAS_DW == 1 - -template <> -class TraceResolverLinuxImpl - : public TraceResolverLinuxBase { -public: - TraceResolverLinuxImpl() : _dwfl_handle_initialized(false) {} - - ResolvedTrace resolve(ResolvedTrace trace) override { - using namespace details; - - Dwarf_Addr trace_addr = reinterpret_cast(trace.addr); - - if (!_dwfl_handle_initialized) { - // initialize dwfl... - _dwfl_cb.reset(new Dwfl_Callbacks); - _dwfl_cb->find_elf = &dwfl_linux_proc_find_elf; - _dwfl_cb->find_debuginfo = &dwfl_standard_find_debuginfo; - _dwfl_cb->debuginfo_path = 0; - - _dwfl_handle.reset(dwfl_begin(_dwfl_cb.get())); - _dwfl_handle_initialized = true; - - if (!_dwfl_handle) { - return trace; - } - - // ...from the current process. - dwfl_report_begin(_dwfl_handle.get()); - int r = dwfl_linux_proc_report(_dwfl_handle.get(), getpid()); - dwfl_report_end(_dwfl_handle.get(), NULL, NULL); - if (r < 0) { - return trace; - } - } - - if (!_dwfl_handle) { - return trace; - } - - // find the module (binary object) that contains the trace's address. - // This is not using any debug information, but the addresses ranges of - // all the currently loaded binary object. - Dwfl_Module *mod = dwfl_addrmodule(_dwfl_handle.get(), trace_addr); - if (mod) { - // now that we found it, lets get the name of it, this will be the - // full path to the running binary or one of the loaded library. - const char *module_name = dwfl_module_info(mod, 0, 0, 0, 0, 0, 0, 0); - if (module_name) { - trace.object_filename = module_name; - } - // We also look after the name of the symbol, equal or before this - // address. This is found by walking the symtab. We should get the - // symbol corresponding to the function (mangled) containing the - // address. If the code corresponding to the address was inlined, - // this is the name of the out-most inliner function. - const char *sym_name = dwfl_module_addrname(mod, trace_addr); - if (sym_name) { - trace.object_function = demangle(sym_name); - } - } - - // now let's get serious, and find out the source location (file and - // line number) of the address. - - // This function will look in .debug_aranges for the address and map it - // to the location of the compilation unit DIE in .debug_info and - // return it. - Dwarf_Addr mod_bias = 0; - Dwarf_Die *cudie = dwfl_module_addrdie(mod, trace_addr, &mod_bias); - -#if 1 - if (!cudie) { - // Sadly clang does not generate the section .debug_aranges, thus - // dwfl_module_addrdie will fail early. Clang doesn't either set - // the lowpc/highpc/range info for every compilation unit. - // - // So in order to save the world: - // for every compilation unit, we will iterate over every single - // DIEs. Normally functions should have a lowpc/highpc/range, which - // we will use to infer the compilation unit. - - // note that this is probably badly inefficient. - while ((cudie = dwfl_module_nextcu(mod, cudie, &mod_bias))) { - Dwarf_Die die_mem; - Dwarf_Die *fundie = - find_fundie_by_pc(cudie, trace_addr - mod_bias, &die_mem); - if (fundie) { - break; - } - } - } -#endif - -//#define BACKWARD_I_DO_NOT_RECOMMEND_TO_ENABLE_THIS_HORRIBLE_PIECE_OF_CODE -#ifdef BACKWARD_I_DO_NOT_RECOMMEND_TO_ENABLE_THIS_HORRIBLE_PIECE_OF_CODE - if (!cudie) { - // If it's still not enough, lets dive deeper in the shit, and try - // to save the world again: for every compilation unit, we will - // load the corresponding .debug_line section, and see if we can - // find our address in it. - - Dwarf_Addr cfi_bias; - Dwarf_CFI *cfi_cache = dwfl_module_eh_cfi(mod, &cfi_bias); - - Dwarf_Addr bias; - while ((cudie = dwfl_module_nextcu(mod, cudie, &bias))) { - if (dwarf_getsrc_die(cudie, trace_addr - bias)) { - - // ...but if we get a match, it might be a false positive - // because our (address - bias) might as well be valid in a - // different compilation unit. So we throw our last card on - // the table and lookup for the address into the .eh_frame - // section. - - handle frame; - dwarf_cfi_addrframe(cfi_cache, trace_addr - cfi_bias, &frame); - if (frame) { - break; - } - } - } - } -#endif - - if (!cudie) { - return trace; // this time we lost the game :/ - } - - // Now that we have a compilation unit DIE, this function will be able - // to load the corresponding section in .debug_line (if not already - // loaded) and hopefully find the source location mapped to our - // address. - Dwarf_Line *srcloc = dwarf_getsrc_die(cudie, trace_addr - mod_bias); - - if (srcloc) { - const char *srcfile = dwarf_linesrc(srcloc, 0, 0); - if (srcfile) { - trace.source.filename = srcfile; - } - int line = 0, col = 0; - dwarf_lineno(srcloc, &line); - dwarf_linecol(srcloc, &col); - trace.source.line = line; - trace.source.col = col; - } - - deep_first_search_by_pc(cudie, trace_addr - mod_bias, - inliners_search_cb(trace)); - if (trace.source.function.size() == 0) { - // fallback. - trace.source.function = trace.object_function; - } - - return trace; - } - -private: - typedef details::handle > - dwfl_handle_t; - details::handle > - _dwfl_cb; - dwfl_handle_t _dwfl_handle; - bool _dwfl_handle_initialized; - - // defined here because in C++98, template function cannot take locally - // defined types... grrr. - struct inliners_search_cb { - void operator()(Dwarf_Die *die) { - switch (dwarf_tag(die)) { - const char *name; - case DW_TAG_subprogram: - if ((name = dwarf_diename(die))) { - trace.source.function = name; - } - break; - - case DW_TAG_inlined_subroutine: - ResolvedTrace::SourceLoc sloc; - Dwarf_Attribute attr_mem; - - if ((name = dwarf_diename(die))) { - sloc.function = name; - } - if ((name = die_call_file(die))) { - sloc.filename = name; - } - - Dwarf_Word line = 0, col = 0; - dwarf_formudata(dwarf_attr(die, DW_AT_call_line, &attr_mem), &line); - dwarf_formudata(dwarf_attr(die, DW_AT_call_column, &attr_mem), &col); - sloc.line = static_cast(line); - sloc.col = static_cast(col); - - trace.inliners.push_back(sloc); - break; - }; - } - ResolvedTrace &trace; - inliners_search_cb(ResolvedTrace &t) : trace(t) {} - }; - - static bool die_has_pc(Dwarf_Die *die, Dwarf_Addr pc) { - Dwarf_Addr low, high; - - // continuous range - if (dwarf_hasattr(die, DW_AT_low_pc) && dwarf_hasattr(die, DW_AT_high_pc)) { - if (dwarf_lowpc(die, &low) != 0) { - return false; - } - if (dwarf_highpc(die, &high) != 0) { - Dwarf_Attribute attr_mem; - Dwarf_Attribute *attr = dwarf_attr(die, DW_AT_high_pc, &attr_mem); - Dwarf_Word value; - if (dwarf_formudata(attr, &value) != 0) { - return false; - } - high = low + value; - } - return pc >= low && pc < high; - } - - // non-continuous range. - Dwarf_Addr base; - ptrdiff_t offset = 0; - while ((offset = dwarf_ranges(die, offset, &base, &low, &high)) > 0) { - if (pc >= low && pc < high) { - return true; - } - } - return false; - } - - static Dwarf_Die *find_fundie_by_pc(Dwarf_Die *parent_die, Dwarf_Addr pc, - Dwarf_Die *result) { - if (dwarf_child(parent_die, result) != 0) { - return 0; - } - - Dwarf_Die *die = result; - do { - switch (dwarf_tag(die)) { - case DW_TAG_subprogram: - case DW_TAG_inlined_subroutine: - if (die_has_pc(die, pc)) { - return result; - } - }; - bool declaration = false; - Dwarf_Attribute attr_mem; - dwarf_formflag(dwarf_attr(die, DW_AT_declaration, &attr_mem), - &declaration); - if (!declaration) { - // let's be curious and look deeper in the tree, - // function are not necessarily at the first level, but - // might be nested inside a namespace, structure etc. - Dwarf_Die die_mem; - Dwarf_Die *indie = find_fundie_by_pc(die, pc, &die_mem); - if (indie) { - *result = die_mem; - return result; - } - } - } while (dwarf_siblingof(die, result) == 0); - return 0; - } - - template - static bool deep_first_search_by_pc(Dwarf_Die *parent_die, Dwarf_Addr pc, - CB cb) { - Dwarf_Die die_mem; - if (dwarf_child(parent_die, &die_mem) != 0) { - return false; - } - - bool branch_has_pc = false; - Dwarf_Die *die = &die_mem; - do { - bool declaration = false; - Dwarf_Attribute attr_mem; - dwarf_formflag(dwarf_attr(die, DW_AT_declaration, &attr_mem), - &declaration); - if (!declaration) { - // let's be curious and look deeper in the tree, function are - // not necessarily at the first level, but might be nested - // inside a namespace, structure, a function, an inlined - // function etc. - branch_has_pc = deep_first_search_by_pc(die, pc, cb); - } - if (!branch_has_pc) { - branch_has_pc = die_has_pc(die, pc); - } - if (branch_has_pc) { - cb(die); - } - } while (dwarf_siblingof(die, &die_mem) == 0); - return branch_has_pc; - } - - static const char *die_call_file(Dwarf_Die *die) { - Dwarf_Attribute attr_mem; - Dwarf_Word file_idx = 0; - - dwarf_formudata(dwarf_attr(die, DW_AT_call_file, &attr_mem), &file_idx); - - if (file_idx == 0) { - return 0; - } - - Dwarf_Die die_mem; - Dwarf_Die *cudie = dwarf_diecu(die, &die_mem, 0, 0); - if (!cudie) { - return 0; - } - - Dwarf_Files *files = 0; - size_t nfiles; - dwarf_getsrcfiles(cudie, &files, &nfiles); - if (!files) { - return 0; - } - - return dwarf_filesrc(files, file_idx, 0, 0); - } -}; -#endif // BACKWARD_HAS_DW == 1 - -#if BACKWARD_HAS_DWARF == 1 - -template <> -class TraceResolverLinuxImpl - : public TraceResolverLinuxBase { -public: - TraceResolverLinuxImpl() : _dwarf_loaded(false) {} - - ResolvedTrace resolve(ResolvedTrace trace) override { - // trace.addr is a virtual address in memory pointing to some code. - // Let's try to find from which loaded object it comes from. - // The loaded object can be yourself btw. - - Dl_info symbol_info; - int dladdr_result = 0; -#if defined(__GLIBC__) - link_map *link_map; - // We request the link map so we can get information about offsets - dladdr_result = - dladdr1(trace.addr, &symbol_info, reinterpret_cast(&link_map), - RTLD_DL_LINKMAP); -#else - // Android doesn't have dladdr1. Don't use the linker map. - dladdr_result = dladdr(trace.addr, &symbol_info); -#endif - if (!dladdr_result) { - return trace; // dat broken trace... - } - - // Now we get in symbol_info: - // .dli_fname: - // pathname of the shared object that contains the address. - // .dli_fbase: - // where the object is loaded in memory. - // .dli_sname: - // the name of the nearest symbol to trace.addr, we expect a - // function name. - // .dli_saddr: - // the exact address corresponding to .dli_sname. - // - // And in link_map: - // .l_addr: - // difference between the address in the ELF file and the address - // in memory - // l_name: - // absolute pathname where the object was found - - if (symbol_info.dli_sname) { - trace.object_function = demangle(symbol_info.dli_sname); - } - - if (!symbol_info.dli_fname) { - return trace; - } - - trace.object_filename = resolve_exec_path(symbol_info); - dwarf_fileobject &fobj = load_object_with_dwarf(symbol_info.dli_fname); - if (!fobj.dwarf_handle) { - return trace; // sad, we couldn't load the object :( - } - -#if defined(__GLIBC__) - // Convert the address to a module relative one by looking at - // the module's loading address in the link map - Dwarf_Addr address = reinterpret_cast(trace.addr) - - reinterpret_cast(link_map->l_addr); -#else - Dwarf_Addr address = reinterpret_cast(trace.addr); -#endif - - if (trace.object_function.empty()) { - symbol_cache_t::iterator it = fobj.symbol_cache.lower_bound(address); - - if (it != fobj.symbol_cache.end()) { - if (it->first != address) { - if (it != fobj.symbol_cache.begin()) { - --it; - } - } - trace.object_function = demangle(it->second.c_str()); - } - } - - // Get the Compilation Unit DIE for the address - Dwarf_Die die = find_die(fobj, address); - - if (!die) { - return trace; // this time we lost the game :/ - } - - // libdwarf doesn't give us direct access to its objects, it always - // allocates a copy for the caller. We keep that copy alive in a cache - // and we deallocate it later when it's no longer required. - die_cache_entry &die_object = get_die_cache(fobj, die); - if (die_object.isEmpty()) - return trace; // We have no line section for this DIE - - die_linemap_t::iterator it = die_object.line_section.lower_bound(address); - - if (it != die_object.line_section.end()) { - if (it->first != address) { - if (it == die_object.line_section.begin()) { - // If we are on the first item of the line section - // but the address does not match it means that - // the address is below the range of the DIE. Give up. - return trace; - } else { - --it; - } - } - } else { - return trace; // We didn't find the address. - } - - // Get the Dwarf_Line that the address points to and call libdwarf - // to get source file, line and column info. - Dwarf_Line line = die_object.line_buffer[it->second]; - Dwarf_Error error = DW_DLE_NE; - - char *filename; - if (dwarf_linesrc(line, &filename, &error) == DW_DLV_OK) { - trace.source.filename = std::string(filename); - dwarf_dealloc(fobj.dwarf_handle.get(), filename, DW_DLA_STRING); - } - - Dwarf_Unsigned number = 0; - if (dwarf_lineno(line, &number, &error) == DW_DLV_OK) { - trace.source.line = number; - } else { - trace.source.line = 0; - } - - if (dwarf_lineoff_b(line, &number, &error) == DW_DLV_OK) { - trace.source.col = number; - } else { - trace.source.col = 0; - } - - std::vector namespace_stack; - deep_first_search_by_pc(fobj, die, address, namespace_stack, - inliners_search_cb(trace, fobj, die)); - - dwarf_dealloc(fobj.dwarf_handle.get(), die, DW_DLA_DIE); - - return trace; - } - -public: - static int close_dwarf(Dwarf_Debug dwarf) { - return dwarf_finish(dwarf, NULL); - } - -private: - bool _dwarf_loaded; - - typedef details::handle > - dwarf_file_t; - - typedef details::handle > - dwarf_elf_t; - - typedef details::handle > - dwarf_handle_t; - - typedef std::map die_linemap_t; - - typedef std::map die_specmap_t; - - struct die_cache_entry { - die_specmap_t spec_section; - die_linemap_t line_section; - Dwarf_Line *line_buffer; - Dwarf_Signed line_count; - Dwarf_Line_Context line_context; - - inline bool isEmpty() { - return line_buffer == NULL || line_count == 0 || line_context == NULL || - line_section.empty(); - } - - die_cache_entry() : line_buffer(0), line_count(0), line_context(0) {} - - ~die_cache_entry() { - if (line_context) { - dwarf_srclines_dealloc_b(line_context); - } - } - }; - - typedef std::map die_cache_t; - - typedef std::map symbol_cache_t; - - struct dwarf_fileobject { - dwarf_file_t file_handle; - dwarf_elf_t elf_handle; - dwarf_handle_t dwarf_handle; - symbol_cache_t symbol_cache; - - // Die cache - die_cache_t die_cache; - die_cache_entry *current_cu; - }; - - typedef details::hashtable::type - fobj_dwarf_map_t; - fobj_dwarf_map_t _fobj_dwarf_map; - - static bool cstrings_eq(const char *a, const char *b) { - if (!a || !b) { - return false; - } - return strcmp(a, b) == 0; - } - - dwarf_fileobject &load_object_with_dwarf(const std::string &filename_object) { - - if (!_dwarf_loaded) { - // Set the ELF library operating version - // If that fails there's nothing we can do - _dwarf_loaded = elf_version(EV_CURRENT) != EV_NONE; - } - - fobj_dwarf_map_t::iterator it = _fobj_dwarf_map.find(filename_object); - if (it != _fobj_dwarf_map.end()) { - return it->second; - } - - // this new object is empty for now - dwarf_fileobject &r = _fobj_dwarf_map[filename_object]; - - dwarf_file_t file_handle; - file_handle.reset(open(filename_object.c_str(), O_RDONLY)); - if (file_handle.get() < 0) { - return r; - } - - // Try to get an ELF handle. We need to read the ELF sections - // because we want to see if there is a .gnu_debuglink section - // that points to a split debug file - dwarf_elf_t elf_handle; - elf_handle.reset(elf_begin(file_handle.get(), ELF_C_READ, NULL)); - if (!elf_handle) { - return r; - } - - const char *e_ident = elf_getident(elf_handle.get(), 0); - if (!e_ident) { - return r; - } - - // Get the number of sections - // We use the new APIs as elf_getshnum is deprecated - size_t shdrnum = 0; - if (elf_getshdrnum(elf_handle.get(), &shdrnum) == -1) { - return r; - } - - // Get the index to the string section - size_t shdrstrndx = 0; - if (elf_getshdrstrndx(elf_handle.get(), &shdrstrndx) == -1) { - return r; - } - - std::string debuglink; - // Iterate through the ELF sections to try to get a gnu_debuglink - // note and also to cache the symbol table. - // We go the preprocessor way to avoid having to create templated - // classes or using gelf (which might throw a compiler error if 64 bit - // is not supported -#define ELF_GET_DATA(ARCH) \ - Elf_Scn *elf_section = 0; \ - Elf_Data *elf_data = 0; \ - Elf##ARCH##_Shdr *section_header = 0; \ - Elf_Scn *symbol_section = 0; \ - size_t symbol_count = 0; \ - size_t symbol_strings = 0; \ - Elf##ARCH##_Sym *symbol = 0; \ - const char *section_name = 0; \ - \ - while ((elf_section = elf_nextscn(elf_handle.get(), elf_section)) != NULL) { \ - section_header = elf##ARCH##_getshdr(elf_section); \ - if (section_header == NULL) { \ - return r; \ - } \ - \ - if ((section_name = elf_strptr(elf_handle.get(), shdrstrndx, \ - section_header->sh_name)) == NULL) { \ - return r; \ - } \ - \ - if (cstrings_eq(section_name, ".gnu_debuglink")) { \ - elf_data = elf_getdata(elf_section, NULL); \ - if (elf_data && elf_data->d_size > 0) { \ - debuglink = \ - std::string(reinterpret_cast(elf_data->d_buf)); \ - } \ - } \ - \ - switch (section_header->sh_type) { \ - case SHT_SYMTAB: \ - symbol_section = elf_section; \ - symbol_count = section_header->sh_size / section_header->sh_entsize; \ - symbol_strings = section_header->sh_link; \ - break; \ - \ - /* We use .dynsyms as a last resort, we prefer .symtab */ \ - case SHT_DYNSYM: \ - if (!symbol_section) { \ - symbol_section = elf_section; \ - symbol_count = section_header->sh_size / section_header->sh_entsize; \ - symbol_strings = section_header->sh_link; \ - } \ - break; \ - } \ - } \ - \ - if (symbol_section && symbol_count && symbol_strings) { \ - elf_data = elf_getdata(symbol_section, NULL); \ - symbol = reinterpret_cast(elf_data->d_buf); \ - for (size_t i = 0; i < symbol_count; ++i) { \ - int type = ELF##ARCH##_ST_TYPE(symbol->st_info); \ - if (type == STT_FUNC && symbol->st_value > 0) { \ - r.symbol_cache[symbol->st_value] = std::string( \ - elf_strptr(elf_handle.get(), symbol_strings, symbol->st_name)); \ - } \ - ++symbol; \ - } \ - } - - if (e_ident[EI_CLASS] == ELFCLASS32) { - ELF_GET_DATA(32) - } else if (e_ident[EI_CLASS] == ELFCLASS64) { - // libelf might have been built without 64 bit support -#if __LIBELF64 - ELF_GET_DATA(64) -#endif - } - - if (!debuglink.empty()) { - // We have a debuglink section! Open an elf instance on that - // file instead. If we can't open the file, then return - // the elf handle we had already opened. - dwarf_file_t debuglink_file; - debuglink_file.reset(open(debuglink.c_str(), O_RDONLY)); - if (debuglink_file.get() > 0) { - dwarf_elf_t debuglink_elf; - debuglink_elf.reset(elf_begin(debuglink_file.get(), ELF_C_READ, NULL)); - - // If we have a valid elf handle, return the new elf handle - // and file handle and discard the original ones - if (debuglink_elf) { - elf_handle = move(debuglink_elf); - file_handle = move(debuglink_file); - } - } - } - - // Ok, we have a valid ELF handle, let's try to get debug symbols - Dwarf_Debug dwarf_debug; - Dwarf_Error error = DW_DLE_NE; - dwarf_handle_t dwarf_handle; - - int dwarf_result = dwarf_elf_init(elf_handle.get(), DW_DLC_READ, NULL, NULL, - &dwarf_debug, &error); - - // We don't do any special handling for DW_DLV_NO_ENTRY specially. - // If we get an error, or the file doesn't have debug information - // we just return. - if (dwarf_result != DW_DLV_OK) { - return r; - } - - dwarf_handle.reset(dwarf_debug); - - r.file_handle = move(file_handle); - r.elf_handle = move(elf_handle); - r.dwarf_handle = move(dwarf_handle); - - return r; - } - - die_cache_entry &get_die_cache(dwarf_fileobject &fobj, Dwarf_Die die) { - Dwarf_Error error = DW_DLE_NE; - - // Get the die offset, we use it as the cache key - Dwarf_Off die_offset; - if (dwarf_dieoffset(die, &die_offset, &error) != DW_DLV_OK) { - die_offset = 0; - } - - die_cache_t::iterator it = fobj.die_cache.find(die_offset); - - if (it != fobj.die_cache.end()) { - fobj.current_cu = &it->second; - return it->second; - } - - die_cache_entry &de = fobj.die_cache[die_offset]; - fobj.current_cu = &de; - - Dwarf_Addr line_addr; - Dwarf_Small table_count; - - // The addresses in the line section are not fully sorted (they might - // be sorted by block of code belonging to the same file), which makes - // it necessary to do so before searching is possible. - // - // As libdwarf allocates a copy of everything, let's get the contents - // of the line section and keep it around. We also create a map of - // program counter to line table indices so we can search by address - // and get the line buffer index. - // - // To make things more difficult, the same address can span more than - // one line, so we need to keep the index pointing to the first line - // by using insert instead of the map's [ operator. - - // Get the line context for the DIE - if (dwarf_srclines_b(die, 0, &table_count, &de.line_context, &error) == - DW_DLV_OK) { - // Get the source lines for this line context, to be deallocated - // later - if (dwarf_srclines_from_linecontext(de.line_context, &de.line_buffer, - &de.line_count, - &error) == DW_DLV_OK) { - - // Add all the addresses to our map - for (int i = 0; i < de.line_count; i++) { - if (dwarf_lineaddr(de.line_buffer[i], &line_addr, &error) != - DW_DLV_OK) { - line_addr = 0; - } - de.line_section.insert(std::pair(line_addr, i)); - } - } - } - - // For each CU, cache the function DIEs that contain the - // DW_AT_specification attribute. When building with -g3 the function - // DIEs are separated in declaration and specification, with the - // declaration containing only the name and parameters and the - // specification the low/high pc and other compiler attributes. - // - // We cache those specifications so we don't skip over the declarations, - // because they have no pc, and we can do namespace resolution for - // DWARF function names. - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - Dwarf_Die current_die = 0; - if (dwarf_child(die, ¤t_die, &error) == DW_DLV_OK) { - for (;;) { - Dwarf_Die sibling_die = 0; - - Dwarf_Half tag_value; - dwarf_tag(current_die, &tag_value, &error); - - if (tag_value == DW_TAG_subprogram || - tag_value == DW_TAG_inlined_subroutine) { - - Dwarf_Bool has_attr = 0; - if (dwarf_hasattr(current_die, DW_AT_specification, &has_attr, - &error) == DW_DLV_OK) { - if (has_attr) { - Dwarf_Attribute attr_mem; - if (dwarf_attr(current_die, DW_AT_specification, &attr_mem, - &error) == DW_DLV_OK) { - Dwarf_Off spec_offset = 0; - if (dwarf_formref(attr_mem, &spec_offset, &error) == - DW_DLV_OK) { - Dwarf_Off spec_die_offset; - if (dwarf_dieoffset(current_die, &spec_die_offset, &error) == - DW_DLV_OK) { - de.spec_section[spec_offset] = spec_die_offset; - } - } - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - } - } - - int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); - if (result == DW_DLV_ERROR) { - break; - } else if (result == DW_DLV_NO_ENTRY) { - break; - } - - if (current_die != die) { - dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); - current_die = 0; - } - - current_die = sibling_die; - } - } - return de; - } - - static Dwarf_Die get_referenced_die(Dwarf_Debug dwarf, Dwarf_Die die, - Dwarf_Half attr, bool global) { - Dwarf_Error error = DW_DLE_NE; - Dwarf_Attribute attr_mem; - - Dwarf_Die found_die = NULL; - if (dwarf_attr(die, attr, &attr_mem, &error) == DW_DLV_OK) { - Dwarf_Off offset; - int result = 0; - if (global) { - result = dwarf_global_formref(attr_mem, &offset, &error); - } else { - result = dwarf_formref(attr_mem, &offset, &error); - } - - if (result == DW_DLV_OK) { - if (dwarf_offdie(dwarf, offset, &found_die, &error) != DW_DLV_OK) { - found_die = NULL; - } - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - return found_die; - } - - static std::string get_referenced_die_name(Dwarf_Debug dwarf, Dwarf_Die die, - Dwarf_Half attr, bool global) { - Dwarf_Error error = DW_DLE_NE; - std::string value; - - Dwarf_Die found_die = get_referenced_die(dwarf, die, attr, global); - - if (found_die) { - char *name; - if (dwarf_diename(found_die, &name, &error) == DW_DLV_OK) { - if (name) { - value = std::string(name); - } - dwarf_dealloc(dwarf, name, DW_DLA_STRING); - } - dwarf_dealloc(dwarf, found_die, DW_DLA_DIE); - } - - return value; - } - - // Returns a spec DIE linked to the passed one. The caller should - // deallocate the DIE - static Dwarf_Die get_spec_die(dwarf_fileobject &fobj, Dwarf_Die die) { - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - Dwarf_Error error = DW_DLE_NE; - Dwarf_Off die_offset; - if (fobj.current_cu && - dwarf_die_CU_offset(die, &die_offset, &error) == DW_DLV_OK) { - die_specmap_t::iterator it = - fobj.current_cu->spec_section.find(die_offset); - - // If we have a DIE that completes the current one, check if - // that one has the pc we are looking for - if (it != fobj.current_cu->spec_section.end()) { - Dwarf_Die spec_die = 0; - if (dwarf_offdie(dwarf, it->second, &spec_die, &error) == DW_DLV_OK) { - return spec_die; - } - } - } - - // Maybe we have an abstract origin DIE with the function information? - return get_referenced_die(fobj.dwarf_handle.get(), die, - DW_AT_abstract_origin, true); - } - - static bool die_has_pc(dwarf_fileobject &fobj, Dwarf_Die die, Dwarf_Addr pc) { - Dwarf_Addr low_pc = 0, high_pc = 0; - Dwarf_Half high_pc_form = 0; - Dwarf_Form_Class return_class; - Dwarf_Error error = DW_DLE_NE; - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - bool has_lowpc = false; - bool has_highpc = false; - bool has_ranges = false; - - if (dwarf_lowpc(die, &low_pc, &error) == DW_DLV_OK) { - // If we have a low_pc check if there is a high pc. - // If we don't have a high pc this might mean we have a base - // address for the ranges list or just an address. - has_lowpc = true; - - if (dwarf_highpc_b(die, &high_pc, &high_pc_form, &return_class, &error) == - DW_DLV_OK) { - // We do have a high pc. In DWARF 4+ this is an offset from the - // low pc, but in earlier versions it's an absolute address. - - has_highpc = true; - // In DWARF 2/3 this would be a DW_FORM_CLASS_ADDRESS - if (return_class == DW_FORM_CLASS_CONSTANT) { - high_pc = low_pc + high_pc; - } - - // We have low and high pc, check if our address - // is in that range - return pc >= low_pc && pc < high_pc; - } - } else { - // Reset the low_pc, in case dwarf_lowpc failing set it to some - // undefined value. - low_pc = 0; - } - - // Check if DW_AT_ranges is present and search for the PC in the - // returned ranges list. We always add the low_pc, as it not set it will - // be 0, in case we had a DW_AT_low_pc and DW_AT_ranges pair - bool result = false; - - Dwarf_Attribute attr; - if (dwarf_attr(die, DW_AT_ranges, &attr, &error) == DW_DLV_OK) { - - Dwarf_Off offset; - if (dwarf_global_formref(attr, &offset, &error) == DW_DLV_OK) { - Dwarf_Ranges *ranges; - Dwarf_Signed ranges_count = 0; - Dwarf_Unsigned byte_count = 0; - - if (dwarf_get_ranges_a(dwarf, offset, die, &ranges, &ranges_count, - &byte_count, &error) == DW_DLV_OK) { - has_ranges = ranges_count != 0; - for (int i = 0; i < ranges_count; i++) { - if (ranges[i].dwr_addr1 != 0 && - pc >= ranges[i].dwr_addr1 + low_pc && - pc < ranges[i].dwr_addr2 + low_pc) { - result = true; - break; - } - } - dwarf_ranges_dealloc(dwarf, ranges, ranges_count); - } - } - } - - // Last attempt. We might have a single address set as low_pc. - if (!result && low_pc != 0 && pc == low_pc) { - result = true; - } - - // If we don't have lowpc, highpc and ranges maybe this DIE is a - // declaration that relies on a DW_AT_specification DIE that happens - // later. Use the specification cache we filled when we loaded this CU. - if (!result && (!has_lowpc && !has_highpc && !has_ranges)) { - Dwarf_Die spec_die = get_spec_die(fobj, die); - if (spec_die) { - result = die_has_pc(fobj, spec_die, pc); - dwarf_dealloc(dwarf, spec_die, DW_DLA_DIE); - } - } - - return result; - } - - static void get_type(Dwarf_Debug dwarf, Dwarf_Die die, std::string &type) { - Dwarf_Error error = DW_DLE_NE; - - Dwarf_Die child = 0; - if (dwarf_child(die, &child, &error) == DW_DLV_OK) { - get_type(dwarf, child, type); - } - - if (child) { - type.insert(0, "::"); - dwarf_dealloc(dwarf, child, DW_DLA_DIE); - } - - char *name; - if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { - type.insert(0, std::string(name)); - dwarf_dealloc(dwarf, name, DW_DLA_STRING); - } else { - type.insert(0, ""); - } - } - - static std::string get_type_by_signature(Dwarf_Debug dwarf, Dwarf_Die die) { - Dwarf_Error error = DW_DLE_NE; - - Dwarf_Sig8 signature; - Dwarf_Bool has_attr = 0; - if (dwarf_hasattr(die, DW_AT_signature, &has_attr, &error) == DW_DLV_OK) { - if (has_attr) { - Dwarf_Attribute attr_mem; - if (dwarf_attr(die, DW_AT_signature, &attr_mem, &error) == DW_DLV_OK) { - if (dwarf_formsig8(attr_mem, &signature, &error) != DW_DLV_OK) { - return std::string(""); - } - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - } - - Dwarf_Unsigned next_cu_header; - Dwarf_Sig8 tu_signature; - std::string result; - bool found = false; - - while (dwarf_next_cu_header_d(dwarf, 0, 0, 0, 0, 0, 0, 0, &tu_signature, 0, - &next_cu_header, 0, &error) == DW_DLV_OK) { - - if (strncmp(signature.signature, tu_signature.signature, 8) == 0) { - Dwarf_Die type_cu_die = 0; - if (dwarf_siblingof_b(dwarf, 0, 0, &type_cu_die, &error) == DW_DLV_OK) { - Dwarf_Die child_die = 0; - if (dwarf_child(type_cu_die, &child_die, &error) == DW_DLV_OK) { - get_type(dwarf, child_die, result); - found = !result.empty(); - dwarf_dealloc(dwarf, child_die, DW_DLA_DIE); - } - dwarf_dealloc(dwarf, type_cu_die, DW_DLA_DIE); - } - } - } - - if (found) { - while (dwarf_next_cu_header_d(dwarf, 0, 0, 0, 0, 0, 0, 0, 0, 0, - &next_cu_header, 0, &error) == DW_DLV_OK) { - // Reset the cu header state. Unfortunately, libdwarf's - // next_cu_header API keeps its own iterator per Dwarf_Debug - // that can't be reset. We need to keep fetching elements until - // the end. - } - } else { - // If we couldn't resolve the type just print out the signature - std::ostringstream string_stream; - string_stream << "<0x" << std::hex << std::setfill('0'); - for (int i = 0; i < 8; ++i) { - string_stream << std::setw(2) << std::hex - << (int)(unsigned char)(signature.signature[i]); - } - string_stream << ">"; - result = string_stream.str(); - } - return result; - } - - struct type_context_t { - bool is_const; - bool is_typedef; - bool has_type; - bool has_name; - std::string text; - - type_context_t() - : is_const(false), is_typedef(false), has_type(false), has_name(false) { - } - }; - - // Types are resolved from right to left: we get the variable name first - // and then all specifiers (like const or pointer) in a chain of DW_AT_type - // DIEs. Call this function recursively until we get a complete type - // string. - static void set_parameter_string(dwarf_fileobject &fobj, Dwarf_Die die, - type_context_t &context) { - char *name; - Dwarf_Error error = DW_DLE_NE; - - // typedefs contain also the base type, so we skip it and only - // print the typedef name - if (!context.is_typedef) { - if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { - if (!context.text.empty()) { - context.text.insert(0, " "); - } - context.text.insert(0, std::string(name)); - dwarf_dealloc(fobj.dwarf_handle.get(), name, DW_DLA_STRING); - } - } else { - context.is_typedef = false; - context.has_type = true; - if (context.is_const) { - context.text.insert(0, "const "); - context.is_const = false; - } - } - - bool next_type_is_const = false; - bool is_keyword = true; - - Dwarf_Half tag = 0; - Dwarf_Bool has_attr = 0; - if (dwarf_tag(die, &tag, &error) == DW_DLV_OK) { - switch (tag) { - case DW_TAG_structure_type: - case DW_TAG_union_type: - case DW_TAG_class_type: - case DW_TAG_enumeration_type: - context.has_type = true; - if (dwarf_hasattr(die, DW_AT_signature, &has_attr, &error) == - DW_DLV_OK) { - // If we have a signature it means the type is defined - // in .debug_types, so we need to load the DIE pointed - // at by the signature and resolve it - if (has_attr) { - std::string type = - get_type_by_signature(fobj.dwarf_handle.get(), die); - if (context.is_const) - type.insert(0, "const "); - - if (!context.text.empty()) - context.text.insert(0, " "); - context.text.insert(0, type); - } - - // Treat enums like typedefs, and skip printing its - // base type - context.is_typedef = (tag == DW_TAG_enumeration_type); - } - break; - case DW_TAG_const_type: - next_type_is_const = true; - break; - case DW_TAG_pointer_type: - context.text.insert(0, "*"); - break; - case DW_TAG_reference_type: - context.text.insert(0, "&"); - break; - case DW_TAG_restrict_type: - context.text.insert(0, "restrict "); - break; - case DW_TAG_rvalue_reference_type: - context.text.insert(0, "&&"); - break; - case DW_TAG_volatile_type: - context.text.insert(0, "volatile "); - break; - case DW_TAG_typedef: - // Propagate the const-ness to the next type - // as typedefs are linked to its base type - next_type_is_const = context.is_const; - context.is_typedef = true; - context.has_type = true; - break; - case DW_TAG_base_type: - context.has_type = true; - break; - case DW_TAG_formal_parameter: - context.has_name = true; - break; - default: - is_keyword = false; - break; - } - } - - if (!is_keyword && context.is_const) { - context.text.insert(0, "const "); - } - - context.is_const = next_type_is_const; - - Dwarf_Die ref = - get_referenced_die(fobj.dwarf_handle.get(), die, DW_AT_type, true); - if (ref) { - set_parameter_string(fobj, ref, context); - dwarf_dealloc(fobj.dwarf_handle.get(), ref, DW_DLA_DIE); - } - - if (!context.has_type && context.has_name) { - context.text.insert(0, "void "); - context.has_type = true; - } - } - - // Resolve the function return type and parameters - static void set_function_parameters(std::string &function_name, - std::vector &ns, - dwarf_fileobject &fobj, Dwarf_Die die) { - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - Dwarf_Error error = DW_DLE_NE; - Dwarf_Die current_die = 0; - std::string parameters; - bool has_spec = true; - // Check if we have a spec DIE. If we do we use it as it contains - // more information, like parameter names. - Dwarf_Die spec_die = get_spec_die(fobj, die); - if (!spec_die) { - has_spec = false; - spec_die = die; - } - - std::vector::const_iterator it = ns.begin(); - std::string ns_name; - for (it = ns.begin(); it < ns.end(); ++it) { - ns_name.append(*it).append("::"); - } - - if (!ns_name.empty()) { - function_name.insert(0, ns_name); - } - - // See if we have a function return type. It can be either on the - // current die or in its spec one (usually true for inlined functions) - std::string return_type = - get_referenced_die_name(dwarf, die, DW_AT_type, true); - if (return_type.empty()) { - return_type = get_referenced_die_name(dwarf, spec_die, DW_AT_type, true); - } - if (!return_type.empty()) { - return_type.append(" "); - function_name.insert(0, return_type); - } - - if (dwarf_child(spec_die, ¤t_die, &error) == DW_DLV_OK) { - for (;;) { - Dwarf_Die sibling_die = 0; - - Dwarf_Half tag_value; - dwarf_tag(current_die, &tag_value, &error); - - if (tag_value == DW_TAG_formal_parameter) { - // Ignore artificial (ie, compiler generated) parameters - bool is_artificial = false; - Dwarf_Attribute attr_mem; - if (dwarf_attr(current_die, DW_AT_artificial, &attr_mem, &error) == - DW_DLV_OK) { - Dwarf_Bool flag = 0; - if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) { - is_artificial = flag != 0; - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - - if (!is_artificial) { - type_context_t context; - set_parameter_string(fobj, current_die, context); - - if (parameters.empty()) { - parameters.append("("); - } else { - parameters.append(", "); - } - parameters.append(context.text); - } - } - - int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); - if (result == DW_DLV_ERROR) { - break; - } else if (result == DW_DLV_NO_ENTRY) { - break; - } - - if (current_die != die) { - dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); - current_die = 0; - } - - current_die = sibling_die; - } - } - if (parameters.empty()) - parameters = "("; - parameters.append(")"); - - // If we got a spec DIE we need to deallocate it - if (has_spec) - dwarf_dealloc(dwarf, spec_die, DW_DLA_DIE); - - function_name.append(parameters); - } - - // defined here because in C++98, template function cannot take locally - // defined types... grrr. - struct inliners_search_cb { - void operator()(Dwarf_Die die, std::vector &ns) { - Dwarf_Error error = DW_DLE_NE; - Dwarf_Half tag_value; - Dwarf_Attribute attr_mem; - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - - dwarf_tag(die, &tag_value, &error); - - switch (tag_value) { - char *name; - case DW_TAG_subprogram: - if (!trace.source.function.empty()) - break; - if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { - trace.source.function = std::string(name); - dwarf_dealloc(dwarf, name, DW_DLA_STRING); - } else { - // We don't have a function name in this DIE. - // Check if there is a referenced non-defining - // declaration. - trace.source.function = - get_referenced_die_name(dwarf, die, DW_AT_abstract_origin, true); - if (trace.source.function.empty()) { - trace.source.function = - get_referenced_die_name(dwarf, die, DW_AT_specification, true); - } - } - - // Append the function parameters, if available - set_function_parameters(trace.source.function, ns, fobj, die); - - // If the object function name is empty, it's possible that - // there is no dynamic symbol table (maybe the executable - // was stripped or not built with -rdynamic). See if we have - // a DWARF linkage name to use instead. We try both - // linkage_name and MIPS_linkage_name because the MIPS tag - // was the unofficial one until it was adopted in DWARF4. - // Old gcc versions generate MIPS_linkage_name - if (trace.object_function.empty()) { - details::demangler demangler; - - if (dwarf_attr(die, DW_AT_linkage_name, &attr_mem, &error) != - DW_DLV_OK) { - if (dwarf_attr(die, DW_AT_MIPS_linkage_name, &attr_mem, &error) != - DW_DLV_OK) { - break; - } - } - - char *linkage; - if (dwarf_formstring(attr_mem, &linkage, &error) == DW_DLV_OK) { - trace.object_function = demangler.demangle(linkage); - dwarf_dealloc(dwarf, linkage, DW_DLA_STRING); - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - break; - - case DW_TAG_inlined_subroutine: - ResolvedTrace::SourceLoc sloc; - - if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { - sloc.function = std::string(name); - dwarf_dealloc(dwarf, name, DW_DLA_STRING); - } else { - // We don't have a name for this inlined DIE, it could - // be that there is an abstract origin instead. - // Get the DW_AT_abstract_origin value, which is a - // reference to the source DIE and try to get its name - sloc.function = - get_referenced_die_name(dwarf, die, DW_AT_abstract_origin, true); - } - - set_function_parameters(sloc.function, ns, fobj, die); - - std::string file = die_call_file(dwarf, die, cu_die); - if (!file.empty()) - sloc.filename = file; - - Dwarf_Unsigned number = 0; - if (dwarf_attr(die, DW_AT_call_line, &attr_mem, &error) == DW_DLV_OK) { - if (dwarf_formudata(attr_mem, &number, &error) == DW_DLV_OK) { - sloc.line = number; - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - - if (dwarf_attr(die, DW_AT_call_column, &attr_mem, &error) == - DW_DLV_OK) { - if (dwarf_formudata(attr_mem, &number, &error) == DW_DLV_OK) { - sloc.col = number; - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - - trace.inliners.push_back(sloc); - break; - }; - } - ResolvedTrace &trace; - dwarf_fileobject &fobj; - Dwarf_Die cu_die; - inliners_search_cb(ResolvedTrace &t, dwarf_fileobject &f, Dwarf_Die c) - : trace(t), fobj(f), cu_die(c) {} - }; - - static Dwarf_Die find_fundie_by_pc(dwarf_fileobject &fobj, - Dwarf_Die parent_die, Dwarf_Addr pc, - Dwarf_Die result) { - Dwarf_Die current_die = 0; - Dwarf_Error error = DW_DLE_NE; - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - - if (dwarf_child(parent_die, ¤t_die, &error) != DW_DLV_OK) { - return NULL; - } - - for (;;) { - Dwarf_Die sibling_die = 0; - Dwarf_Half tag_value; - dwarf_tag(current_die, &tag_value, &error); - - switch (tag_value) { - case DW_TAG_subprogram: - case DW_TAG_inlined_subroutine: - if (die_has_pc(fobj, current_die, pc)) { - return current_die; - } - }; - bool declaration = false; - Dwarf_Attribute attr_mem; - if (dwarf_attr(current_die, DW_AT_declaration, &attr_mem, &error) == - DW_DLV_OK) { - Dwarf_Bool flag = 0; - if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) { - declaration = flag != 0; - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - - if (!declaration) { - // let's be curious and look deeper in the tree, functions are - // not necessarily at the first level, but might be nested - // inside a namespace, structure, a function, an inlined - // function etc. - Dwarf_Die die_mem = 0; - Dwarf_Die indie = find_fundie_by_pc(fobj, current_die, pc, die_mem); - if (indie) { - result = die_mem; - return result; - } - } - - int res = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); - if (res == DW_DLV_ERROR) { - return NULL; - } else if (res == DW_DLV_NO_ENTRY) { - break; - } - - if (current_die != parent_die) { - dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); - current_die = 0; - } - - current_die = sibling_die; - } - return NULL; - } - - template - static bool deep_first_search_by_pc(dwarf_fileobject &fobj, - Dwarf_Die parent_die, Dwarf_Addr pc, - std::vector &ns, CB cb) { - Dwarf_Die current_die = 0; - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - Dwarf_Error error = DW_DLE_NE; - - if (dwarf_child(parent_die, ¤t_die, &error) != DW_DLV_OK) { - return false; - } - - bool branch_has_pc = false; - bool has_namespace = false; - for (;;) { - Dwarf_Die sibling_die = 0; - - Dwarf_Half tag; - if (dwarf_tag(current_die, &tag, &error) == DW_DLV_OK) { - if (tag == DW_TAG_namespace || tag == DW_TAG_class_type) { - char *ns_name = NULL; - if (dwarf_diename(current_die, &ns_name, &error) == DW_DLV_OK) { - if (ns_name) { - ns.push_back(std::string(ns_name)); - } else { - ns.push_back(""); - } - dwarf_dealloc(dwarf, ns_name, DW_DLA_STRING); - } else { - ns.push_back(""); - } - has_namespace = true; - } - } - - bool declaration = false; - Dwarf_Attribute attr_mem; - if (tag != DW_TAG_class_type && - dwarf_attr(current_die, DW_AT_declaration, &attr_mem, &error) == - DW_DLV_OK) { - Dwarf_Bool flag = 0; - if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) { - declaration = flag != 0; - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - - if (!declaration) { - // let's be curious and look deeper in the tree, function are - // not necessarily at the first level, but might be nested - // inside a namespace, structure, a function, an inlined - // function etc. - branch_has_pc = deep_first_search_by_pc(fobj, current_die, pc, ns, cb); - } - - if (!branch_has_pc) { - branch_has_pc = die_has_pc(fobj, current_die, pc); - } - - if (branch_has_pc) { - cb(current_die, ns); - } - - int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); - if (result == DW_DLV_ERROR) { - return false; - } else if (result == DW_DLV_NO_ENTRY) { - break; - } - - if (current_die != parent_die) { - dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); - current_die = 0; - } - - if (has_namespace) { - has_namespace = false; - ns.pop_back(); - } - current_die = sibling_die; - } - - if (has_namespace) { - ns.pop_back(); - } - return branch_has_pc; - } - - static std::string die_call_file(Dwarf_Debug dwarf, Dwarf_Die die, - Dwarf_Die cu_die) { - Dwarf_Attribute attr_mem; - Dwarf_Error error = DW_DLE_NE; - Dwarf_Unsigned file_index; - - std::string file; - - if (dwarf_attr(die, DW_AT_call_file, &attr_mem, &error) == DW_DLV_OK) { - if (dwarf_formudata(attr_mem, &file_index, &error) != DW_DLV_OK) { - file_index = 0; - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - - if (file_index == 0) { - return file; - } - - char **srcfiles = 0; - Dwarf_Signed file_count = 0; - if (dwarf_srcfiles(cu_die, &srcfiles, &file_count, &error) == DW_DLV_OK) { - if (file_count > 0 && file_index <= static_cast(file_count)) { - file = std::string(srcfiles[file_index - 1]); - } - - // Deallocate all strings! - for (int i = 0; i < file_count; ++i) { - dwarf_dealloc(dwarf, srcfiles[i], DW_DLA_STRING); - } - dwarf_dealloc(dwarf, srcfiles, DW_DLA_LIST); - } - } - return file; - } - - Dwarf_Die find_die(dwarf_fileobject &fobj, Dwarf_Addr addr) { - // Let's get to work! First see if we have a debug_aranges section so - // we can speed up the search - - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - Dwarf_Error error = DW_DLE_NE; - Dwarf_Arange *aranges; - Dwarf_Signed arange_count; - - Dwarf_Die returnDie; - bool found = false; - if (dwarf_get_aranges(dwarf, &aranges, &arange_count, &error) != - DW_DLV_OK) { - aranges = NULL; - } - - if (aranges) { - // We have aranges. Get the one where our address is. - Dwarf_Arange arange; - if (dwarf_get_arange(aranges, arange_count, addr, &arange, &error) == - DW_DLV_OK) { - - // We found our address. Get the compilation-unit DIE offset - // represented by the given address range. - Dwarf_Off cu_die_offset; - if (dwarf_get_cu_die_offset(arange, &cu_die_offset, &error) == - DW_DLV_OK) { - // Get the DIE at the offset returned by the aranges search. - // We set is_info to 1 to specify that the offset is from - // the .debug_info section (and not .debug_types) - int dwarf_result = - dwarf_offdie_b(dwarf, cu_die_offset, 1, &returnDie, &error); - - found = dwarf_result == DW_DLV_OK; - } - dwarf_dealloc(dwarf, arange, DW_DLA_ARANGE); - } - } - - if (found) - return returnDie; // The caller is responsible for freeing the die - - // The search for aranges failed. Try to find our address by scanning - // all compilation units. - Dwarf_Unsigned next_cu_header; - Dwarf_Half tag = 0; - returnDie = 0; - - while (!found && - dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, - &next_cu_header, 0, &error) == DW_DLV_OK) { - - if (returnDie) - dwarf_dealloc(dwarf, returnDie, DW_DLA_DIE); - - if (dwarf_siblingof(dwarf, 0, &returnDie, &error) == DW_DLV_OK) { - if ((dwarf_tag(returnDie, &tag, &error) == DW_DLV_OK) && - tag == DW_TAG_compile_unit) { - if (die_has_pc(fobj, returnDie, addr)) { - found = true; - } - } - } - } - - if (found) { - while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, - &next_cu_header, 0, &error) == DW_DLV_OK) { - // Reset the cu header state. Libdwarf's next_cu_header API - // keeps its own iterator per Dwarf_Debug that can't be reset. - // We need to keep fetching elements until the end. - } - } - - if (found) - return returnDie; - - // We couldn't find any compilation units with ranges or a high/low pc. - // Try again by looking at all DIEs in all compilation units. - Dwarf_Die cudie; - while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, - &next_cu_header, 0, &error) == DW_DLV_OK) { - if (dwarf_siblingof(dwarf, 0, &cudie, &error) == DW_DLV_OK) { - Dwarf_Die die_mem = 0; - Dwarf_Die resultDie = find_fundie_by_pc(fobj, cudie, addr, die_mem); - - if (resultDie) { - found = true; - break; - } - } - } - - if (found) { - while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, - &next_cu_header, 0, &error) == DW_DLV_OK) { - // Reset the cu header state. Libdwarf's next_cu_header API - // keeps its own iterator per Dwarf_Debug that can't be reset. - // We need to keep fetching elements until the end. - } - } - - if (found) - return cudie; - - // We failed. - return NULL; - } -}; -#endif // BACKWARD_HAS_DWARF == 1 - -template <> -class TraceResolverImpl - : public TraceResolverLinuxImpl {}; - -#endif // BACKWARD_SYSTEM_LINUX - -#ifdef BACKWARD_SYSTEM_DARWIN - -template class TraceResolverDarwinImpl; - -template <> -class TraceResolverDarwinImpl - : public TraceResolverImplBase { -public: - void load_addresses(void *const*addresses, int address_count) override { - if (address_count == 0) { - return; - } - _symbols.reset(backtrace_symbols(addresses, address_count)); - } - - ResolvedTrace resolve(ResolvedTrace trace) override { - // parse: - // + - char *filename = _symbols[trace.idx]; - - // skip " " - while (*filename && *filename != ' ') - filename++; - while (*filename == ' ') - filename++; - - // find start of from end ( may contain a space) - char *p = filename + strlen(filename) - 1; - // skip to start of " + " - while (p > filename && *p != ' ') - p--; - while (p > filename && *p == ' ') - p--; - while (p > filename && *p != ' ') - p--; - while (p > filename && *p == ' ') - p--; - char *funcname_end = p + 1; - - // skip to start of "" - while (p > filename && *p != ' ') - p--; - char *funcname = p + 1; - - // skip to start of " " - while (p > filename && *p == ' ') - p--; - while (p > filename && *p != ' ') - p--; - while (p > filename && *p == ' ') - p--; - - // skip "", handling the case where it contains a - char *filename_end = p + 1; - if (p == filename) { - // something went wrong, give up - filename_end = filename + strlen(filename); - funcname = filename_end; - } - trace.object_filename.assign( - filename, filename_end); // ok even if filename_end is the ending \0 - // (then we assign entire string) - - if (*funcname) { // if it's not end of string - *funcname_end = '\0'; - - trace.object_function = this->demangle(funcname); - trace.object_function += " "; - trace.object_function += (funcname_end + 1); - trace.source.function = trace.object_function; // we cannot do better. - } - return trace; - } - -private: - details::handle _symbols; -}; - -template <> -class TraceResolverImpl - : public TraceResolverDarwinImpl {}; - -#endif // BACKWARD_SYSTEM_DARWIN - -#ifdef BACKWARD_SYSTEM_WINDOWS - -// Load all symbol info -// Based on: -// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227 - -struct module_data { - std::string image_name; - std::string module_name; - void *base_address; - DWORD load_size; -}; - -class get_mod_info { - HANDLE process; - static const int buffer_length = 4096; - -public: - get_mod_info(HANDLE h) : process(h) {} - - module_data operator()(HMODULE module) { - module_data ret; - char temp[buffer_length]; - MODULEINFO mi; - - GetModuleInformation(process, module, &mi, sizeof(mi)); - ret.base_address = mi.lpBaseOfDll; - ret.load_size = mi.SizeOfImage; - - GetModuleFileNameExA(process, module, temp, sizeof(temp)); - ret.image_name = temp; - GetModuleBaseNameA(process, module, temp, sizeof(temp)); - ret.module_name = temp; - std::vector img(ret.image_name.begin(), ret.image_name.end()); - std::vector mod(ret.module_name.begin(), ret.module_name.end()); - SymLoadModule64(process, 0, &img[0], &mod[0], (DWORD64)ret.base_address, - ret.load_size); - return ret; - } -}; - -template <> class TraceResolverImpl - : public TraceResolverImplBase { -public: - TraceResolverImpl() { - - HANDLE process = GetCurrentProcess(); - - std::vector modules; - DWORD cbNeeded; - std::vector module_handles(1); - SymInitialize(process, NULL, false); - DWORD symOptions = SymGetOptions(); - symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; - SymSetOptions(symOptions); - EnumProcessModules(process, &module_handles[0], - module_handles.size() * sizeof(HMODULE), &cbNeeded); - module_handles.resize(cbNeeded / sizeof(HMODULE)); - EnumProcessModules(process, &module_handles[0], - module_handles.size() * sizeof(HMODULE), &cbNeeded); - std::transform(module_handles.begin(), module_handles.end(), - std::back_inserter(modules), get_mod_info(process)); - void *base = modules[0].base_address; - IMAGE_NT_HEADERS *h = ImageNtHeader(base); - image_type = h->FileHeader.Machine; - } - - static const int max_sym_len = 255; - struct symbol_t { - SYMBOL_INFO sym; - char buffer[max_sym_len]; - } sym; - - DWORD64 displacement; - - ResolvedTrace resolve(ResolvedTrace t) override { - HANDLE process = GetCurrentProcess(); - - char name[256]; - - memset(&sym, 0, sizeof(sym)); - sym.sym.SizeOfStruct = sizeof(SYMBOL_INFO); - sym.sym.MaxNameLen = max_sym_len; - - if (!SymFromAddr(process, (ULONG64)t.addr, &displacement, &sym.sym)) { - // TODO: error handling everywhere - char* lpMsgBuf; - DWORD dw = GetLastError(); - - if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (char*)&lpMsgBuf, 0, NULL)) { - std::fprintf(stderr, "%s\n", lpMsgBuf); - LocalFree(lpMsgBuf); - } - - // abort(); - } - UnDecorateSymbolName(sym.sym.Name, (PSTR)name, 256, UNDNAME_COMPLETE); - - DWORD offset = 0; - IMAGEHLP_LINE line; - if (SymGetLineFromAddr(process, (ULONG64)t.addr, &offset, &line)) { - t.object_filename = line.FileName; - t.source.filename = line.FileName; - t.source.line = line.LineNumber; - t.source.col = offset; - } - - t.source.function = name; - t.object_filename = ""; - t.object_function = name; - - return t; - } - - DWORD machine_type() const { return image_type; } - -private: - DWORD image_type; -}; - -#endif - -class TraceResolver : public TraceResolverImpl {}; - -/*************** CODE SNIPPET ***************/ - -class SourceFile { -public: - typedef std::vector > lines_t; - - SourceFile() {} - SourceFile(const std::string &path) { - // 1. If BACKWARD_CXX_SOURCE_PREFIXES is set then assume it contains - // a colon-separated list of path prefixes. Try prepending each - // to the given path until a valid file is found. - const std::vector &prefixes = get_paths_from_env_variable(); - for (size_t i = 0; i < prefixes.size(); ++i) { - // Double slashes (//) should not be a problem. - std::string new_path = prefixes[i] + '/' + path; - _file.reset(new std::ifstream(new_path.c_str())); - if (is_open()) - break; - } - // 2. If no valid file found then fallback to opening the path as-is. - if (!_file || !is_open()) { - _file.reset(new std::ifstream(path.c_str())); - } - } - bool is_open() const { return _file->is_open(); } - - lines_t &get_lines(unsigned line_start, unsigned line_count, lines_t &lines) { - using namespace std; - // This function make uses of the dumbest algo ever: - // 1) seek(0) - // 2) read lines one by one and discard until line_start - // 3) read line one by one until line_start + line_count - // - // If you are getting snippets many time from the same file, it is - // somewhat a waste of CPU, feel free to benchmark and propose a - // better solution ;) - - _file->clear(); - _file->seekg(0); - string line; - unsigned line_idx; - - for (line_idx = 1; line_idx < line_start; ++line_idx) { - std::getline(*_file, line); - if (!*_file) { - return lines; - } - } - - // think of it like a lambda in C++98 ;) - // but look, I will reuse it two times! - // What a good boy am I. - struct isspace { - bool operator()(char c) { return std::isspace(c); } - }; - - bool started = false; - for (; line_idx < line_start + line_count; ++line_idx) { - getline(*_file, line); - if (!*_file) { - return lines; - } - if (!started) { - if (std::find_if(line.begin(), line.end(), not_isspace()) == line.end()) - continue; - started = true; - } - lines.push_back(make_pair(line_idx, line)); - } - - lines.erase( - std::find_if(lines.rbegin(), lines.rend(), not_isempty()).base(), - lines.end()); - return lines; - } - - lines_t get_lines(unsigned line_start, unsigned line_count) { - lines_t lines; - return get_lines(line_start, line_count, lines); - } - - // there is no find_if_not in C++98, lets do something crappy to - // workaround. - struct not_isspace { - bool operator()(char c) { return !std::isspace(c); } - }; - // and define this one here because C++98 is not happy with local defined - // struct passed to template functions, fuuuu. - struct not_isempty { - bool operator()(const lines_t::value_type &p) { - return !(std::find_if(p.second.begin(), p.second.end(), not_isspace()) == - p.second.end()); - } - }; - - void swap(SourceFile &b) { _file.swap(b._file); } - -#ifdef BACKWARD_ATLEAST_CXX11 - SourceFile(SourceFile &&from) : _file(nullptr) { swap(from); } - SourceFile &operator=(SourceFile &&from) { - swap(from); - return *this; - } -#else - explicit SourceFile(const SourceFile &from) { - // some sort of poor man's move semantic. - swap(const_cast(from)); - } - SourceFile &operator=(const SourceFile &from) { - // some sort of poor man's move semantic. - swap(const_cast(from)); - return *this; - } -#endif - -private: - details::handle > - _file; - - std::vector get_paths_from_env_variable_impl() { - std::vector paths; - const char *prefixes_str = std::getenv("BACKWARD_CXX_SOURCE_PREFIXES"); - if (prefixes_str && prefixes_str[0]) { - paths = details::split_source_prefixes(prefixes_str); - } - return paths; - } - - const std::vector &get_paths_from_env_variable() { - static std::vector paths = get_paths_from_env_variable_impl(); - return paths; - } - -#ifdef BACKWARD_ATLEAST_CXX11 - SourceFile(const SourceFile &) = delete; - SourceFile &operator=(const SourceFile &) = delete; -#endif -}; - -class SnippetFactory { -public: - typedef SourceFile::lines_t lines_t; - - lines_t get_snippet(const std::string &filename, unsigned line_start, - unsigned context_size) { - - SourceFile &src_file = get_src_file(filename); - unsigned start = line_start - context_size / 2; - return src_file.get_lines(start, context_size); - } - - lines_t get_combined_snippet(const std::string &filename_a, unsigned line_a, - const std::string &filename_b, unsigned line_b, - unsigned context_size) { - SourceFile &src_file_a = get_src_file(filename_a); - SourceFile &src_file_b = get_src_file(filename_b); - - lines_t lines = - src_file_a.get_lines(line_a - context_size / 4, context_size / 2); - src_file_b.get_lines(line_b - context_size / 4, context_size / 2, lines); - return lines; - } - - lines_t get_coalesced_snippet(const std::string &filename, unsigned line_a, - unsigned line_b, unsigned context_size) { - SourceFile &src_file = get_src_file(filename); - - using std::max; - using std::min; - unsigned a = min(line_a, line_b); - unsigned b = max(line_a, line_b); - - if ((b - a) < (context_size / 3)) { - return src_file.get_lines((a + b - context_size + 1) / 2, context_size); - } - - lines_t lines = src_file.get_lines(a - context_size / 4, context_size / 2); - src_file.get_lines(b - context_size / 4, context_size / 2, lines); - return lines; - } - -private: - typedef details::hashtable::type src_files_t; - src_files_t _src_files; - - SourceFile &get_src_file(const std::string &filename) { - src_files_t::iterator it = _src_files.find(filename); - if (it != _src_files.end()) { - return it->second; - } - SourceFile &new_src_file = _src_files[filename]; - new_src_file = SourceFile(filename); - return new_src_file; - } -}; - -/*************** PRINTER ***************/ - -namespace ColorMode { -enum type { automatic, never, always }; -} - -class cfile_streambuf : public std::streambuf { -public: - cfile_streambuf(FILE *_sink) : sink(_sink) {} - int_type underflow() override { return traits_type::eof(); } - int_type overflow(int_type ch) override { - if (traits_type::not_eof(ch) && fputc(ch, sink) != EOF) { - return ch; - } - return traits_type::eof(); - } - - std::streamsize xsputn(const char_type *s, std::streamsize count) override { - return static_cast( - fwrite(s, sizeof *s, static_cast(count), sink)); - } - -#ifdef BACKWARD_ATLEAST_CXX11 -public: - cfile_streambuf(const cfile_streambuf &) = delete; - cfile_streambuf &operator=(const cfile_streambuf &) = delete; -#else -private: - cfile_streambuf(const cfile_streambuf &); - cfile_streambuf &operator=(const cfile_streambuf &); -#endif - -private: - FILE *sink; - std::vector buffer; -}; - -#ifdef BACKWARD_SYSTEM_LINUX - -namespace Color { -enum type { yellow = 33, purple = 35, reset = 39 }; -} // namespace Color - -class Colorize { -public: - Colorize(std::ostream &os) : _os(os), _reset(false), _enabled(false) {} - - void activate(ColorMode::type mode) { _enabled = mode == ColorMode::always; } - - void activate(ColorMode::type mode, FILE *fp) { activate(mode, fileno(fp)); } - - void set_color(Color::type ccode) { - if (!_enabled) - return; - - // I assume that the terminal can handle basic colors. Seriously I - // don't want to deal with all the termcap shit. - _os << "\033[" << static_cast(ccode) << "m"; - _reset = (ccode != Color::reset); - } - - ~Colorize() { - if (_reset) { - set_color(Color::reset); - } - } - -private: - void activate(ColorMode::type mode, int fd) { - activate(mode == ColorMode::automatic && isatty(fd) ? ColorMode::always - : mode); - } - - std::ostream &_os; - bool _reset; - bool _enabled; -}; - -#else // ndef BACKWARD_SYSTEM_LINUX - -namespace Color { -enum type { yellow = 0, purple = 0, reset = 0 }; -} // namespace Color - -class Colorize { -public: - Colorize(std::ostream &) {} - void activate(ColorMode::type) {} - void activate(ColorMode::type, FILE *) {} - void set_color(Color::type) {} -}; - -#endif // BACKWARD_SYSTEM_LINUX - -class Printer { -public: - bool snippet; - ColorMode::type color_mode; - bool address; - bool object; - int inliner_context_size; - int trace_context_size; - - Printer() - : snippet(true), color_mode(ColorMode::automatic), address(false), - object(false), inliner_context_size(5), trace_context_size(7) {} - - template FILE *print(ST &st, FILE *fp = stderr) { - cfile_streambuf obuf(fp); - std::ostream os(&obuf); - Colorize colorize(os); - colorize.activate(color_mode, fp); - print_stacktrace(st, os, colorize); - return fp; - } - - template std::ostream &print(ST &st, std::ostream &os) { - Colorize colorize(os); - colorize.activate(color_mode); - print_stacktrace(st, os, colorize); - return os; - } - - template - FILE *print(IT begin, IT end, FILE *fp = stderr, size_t thread_id = 0) { - cfile_streambuf obuf(fp); - std::ostream os(&obuf); - Colorize colorize(os); - colorize.activate(color_mode, fp); - print_stacktrace(begin, end, os, thread_id, colorize); - return fp; - } - - template - std::ostream &print(IT begin, IT end, std::ostream &os, - size_t thread_id = 0) { - Colorize colorize(os); - colorize.activate(color_mode); - print_stacktrace(begin, end, os, thread_id, colorize); - return os; - } - - TraceResolver const &resolver() const { return _resolver; } - -private: - TraceResolver _resolver; - SnippetFactory _snippets; - - template - void print_stacktrace(ST &st, std::ostream &os, Colorize &colorize) { - print_header(os, st.thread_id()); - _resolver.load_stacktrace(st); - for (size_t trace_idx = st.size(); trace_idx > 0; --trace_idx) { - print_trace(os, _resolver.resolve(st[trace_idx - 1]), colorize); - } - } - - template - void print_stacktrace(IT begin, IT end, std::ostream &os, size_t thread_id, - Colorize &colorize) { - print_header(os, thread_id); - for (; begin != end; ++begin) { - print_trace(os, *begin, colorize); - } - } - - void print_header(std::ostream &os, size_t thread_id) { - os << "Stack trace (most recent call last)"; - if (thread_id) { - os << " in thread " << thread_id; - } - os << ":\n"; - } - - void print_trace(std::ostream &os, const ResolvedTrace &trace, - Colorize &colorize) { - os << "#" << std::left << std::setw(2) << trace.idx << std::right; - bool already_indented = true; - - if (!trace.source.filename.size() || object) { - os << " Object \"" << trace.object_filename << "\", at " << trace.addr - << ", in " << trace.object_function << "\n"; - already_indented = false; - } - - for (size_t inliner_idx = trace.inliners.size(); inliner_idx > 0; - --inliner_idx) { - if (!already_indented) { - os << " "; - } - const ResolvedTrace::SourceLoc &inliner_loc = - trace.inliners[inliner_idx - 1]; - print_source_loc(os, " | ", inliner_loc); - if (snippet) { - print_snippet(os, " | ", inliner_loc, colorize, Color::purple, - inliner_context_size); - } - already_indented = false; - } - - if (trace.source.filename.size()) { - if (!already_indented) { - os << " "; - } - print_source_loc(os, " ", trace.source, trace.addr); - if (snippet) { - print_snippet(os, " ", trace.source, colorize, Color::yellow, - trace_context_size); - } - } - } - - void print_snippet(std::ostream &os, const char *indent, - const ResolvedTrace::SourceLoc &source_loc, - Colorize &colorize, Color::type color_code, - int context_size) { - using namespace std; - typedef SnippetFactory::lines_t lines_t; - - lines_t lines = _snippets.get_snippet(source_loc.filename, source_loc.line, - static_cast(context_size)); - - for (lines_t::const_iterator it = lines.begin(); it != lines.end(); ++it) { - if (it->first == source_loc.line) { - colorize.set_color(color_code); - os << indent << ">"; - } else { - os << indent << " "; - } - os << std::setw(4) << it->first << ": " << it->second << "\n"; - if (it->first == source_loc.line) { - colorize.set_color(Color::reset); - } - } - } - - void print_source_loc(std::ostream &os, const char *indent, - const ResolvedTrace::SourceLoc &source_loc, - void *addr = nullptr) { - os << indent << "Source \"" << source_loc.filename << "\", line " - << source_loc.line << ", in " << source_loc.function; - - if (address && addr != nullptr) { - os << " [" << addr << "]"; - } - os << "\n"; - } -}; - -/*************** SIGNALS HANDLING ***************/ - -#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) - -class SignalHandling { -public: - static std::vector make_default_signals() { - const int posix_signals[] = { - // Signals for which the default action is "Core". - SIGABRT, // Abort signal from abort(3) - SIGBUS, // Bus error (bad memory access) - SIGFPE, // Floating point exception - SIGILL, // Illegal Instruction - SIGIOT, // IOT trap. A synonym for SIGABRT - SIGQUIT, // Quit from keyboard - SIGSEGV, // Invalid memory reference - SIGSYS, // Bad argument to routine (SVr4) - SIGTRAP, // Trace/breakpoint trap - SIGXCPU, // CPU time limit exceeded (4.2BSD) - SIGXFSZ, // File size limit exceeded (4.2BSD) -#if defined(BACKWARD_SYSTEM_DARWIN) - SIGEMT, // emulation instruction executed -#endif - }; - return std::vector(posix_signals, - posix_signals + - sizeof posix_signals / sizeof posix_signals[0]); - } - - SignalHandling(const std::vector &posix_signals = make_default_signals()) - : _loaded(false) { - bool success = true; - - const size_t stack_size = 1024 * 1024 * 8; - _stack_content.reset(static_cast(malloc(stack_size))); - if (_stack_content) { - stack_t ss; - ss.ss_sp = _stack_content.get(); - ss.ss_size = stack_size; - ss.ss_flags = 0; - if (sigaltstack(&ss, nullptr) < 0) { - success = false; - } - } else { - success = false; - } - - for (size_t i = 0; i < posix_signals.size(); ++i) { - struct sigaction action; - memset(&action, 0, sizeof action); - action.sa_flags = - static_cast(SA_SIGINFO | SA_ONSTACK | SA_NODEFER | SA_RESETHAND); - sigfillset(&action.sa_mask); - sigdelset(&action.sa_mask, posix_signals[i]); -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" -#endif - action.sa_sigaction = &sig_handler; -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - - int r = sigaction(posix_signals[i], &action, nullptr); - if (r < 0) - success = false; - } - - _loaded = success; - } - - bool loaded() const { return _loaded; } - - static void handleSignal(int, siginfo_t *info, void *_ctx) { - ucontext_t *uctx = static_cast(_ctx); - - StackTrace st{p::GetCurrentArena()}; - void *error_addr = nullptr; -#ifdef REG_RIP // x86_64 - error_addr = reinterpret_cast(uctx->uc_mcontext.gregs[REG_RIP]); -#elif defined(REG_EIP) // x86_32 - error_addr = reinterpret_cast(uctx->uc_mcontext.gregs[REG_EIP]); -#elif defined(__arm__) - error_addr = reinterpret_cast(uctx->uc_mcontext.arm_pc); -#elif defined(__aarch64__) - #if defined(__APPLE__) - error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__pc); - #else - error_addr = reinterpret_cast(uctx->uc_mcontext.pc); - #endif -#elif defined(__mips__) - error_addr = reinterpret_cast( - reinterpret_cast(&uctx->uc_mcontext)->sc_pc); -#elif defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || \ - defined(__POWERPC__) - error_addr = reinterpret_cast(uctx->uc_mcontext.regs->nip); -#elif defined(__riscv) - error_addr = reinterpret_cast(uctx->uc_mcontext.__gregs[REG_PC]); -#elif defined(__s390x__) - error_addr = reinterpret_cast(uctx->uc_mcontext.psw.addr); -#elif defined(__APPLE__) && defined(__x86_64__) - error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__rip); -#elif defined(__APPLE__) - error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__eip); -#else -#warning ":/ sorry, ain't know no nothing none not of your architecture!" -#endif - if (error_addr) { - st.load_from(error_addr, 32, reinterpret_cast(uctx), - info->si_addr); - } else { - st.load_here(32, reinterpret_cast(uctx), info->si_addr); - } - - Printer printer; - printer.address = true; - printer.print(st, stderr); - -#if (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700) || \ - (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L) - psiginfo(info, nullptr); -#else - (void)info; -#endif - } - -private: - details::handle _stack_content; - bool _loaded; - -#ifdef __GNUC__ - __attribute__((noreturn)) -#endif - static void - sig_handler(int signo, siginfo_t *info, void *_ctx) { - handleSignal(signo, info, _ctx); - - // try to forward the signal. - raise(info->si_signo); - - // terminate the process immediately. - puts("watf? exit"); - _exit(EXIT_FAILURE); - } -}; - -#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN - -#ifdef BACKWARD_SYSTEM_WINDOWS - -class SignalHandling { -public: - SignalHandling(const std::vector & = std::vector()) - : reporter_thread_([]() { - /* We handle crashes in a utility thread: - backward structures and some Windows functions called here - need stack space, which we do not have when we encounter a - stack overflow. - To support reporting stack traces during a stack overflow, - we create a utility thread at startup, which waits until a - crash happens or the program exits normally. */ - - { - std::unique_lock lk(mtx()); - cv().wait(lk, [] { return crashed() != crash_status::running; }); - } - if (crashed() == crash_status::crashed) { - handle_stacktrace(skip_recs()); - } - { - std::unique_lock lk(mtx()); - crashed() = crash_status::ending; - } - cv().notify_one(); - }) { - SetUnhandledExceptionFilter(crash_handler); - - signal(SIGABRT, signal_handler); - _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); - - std::set_terminate(&terminator); -#ifndef BACKWARD_ATLEAST_CXX17 - std::set_unexpected(&terminator); -#endif - _set_purecall_handler(&terminator); - _set_invalid_parameter_handler(&invalid_parameter_handler); - } - bool loaded() const { return true; } - - ~SignalHandling() { - { - std::unique_lock lk(mtx()); - crashed() = crash_status::normal_exit; - } - - cv().notify_one(); - - reporter_thread_.join(); - } - -private: - static CONTEXT *ctx() { - static CONTEXT data; - return &data; - } - - enum class crash_status { running, crashed, normal_exit, ending }; - - static crash_status &crashed() { - static crash_status data; - return data; - } - - static std::mutex &mtx() { - static std::mutex data; - return data; - } - - static std::condition_variable &cv() { - static std::condition_variable data; - return data; - } - - static HANDLE &thread_handle() { - static HANDLE handle; - return handle; - } - - std::thread reporter_thread_; - - // TODO: how not to hardcode these? - static const constexpr int signal_skip_recs = -#ifdef __clang__ - // With clang, RtlCaptureContext also captures the stack frame of the - // current function Below that, there ar 3 internal Windows functions - 4 -#else - // With MSVC cl, RtlCaptureContext misses the stack frame of the current - // function The first entries during StackWalk are the 3 internal Windows - // functions - 3 -#endif - ; - - static int &skip_recs() { - static int data; - return data; - } - - static inline void terminator() { - crash_handler(signal_skip_recs); - abort(); - } - - static inline void signal_handler(int) { - crash_handler(signal_skip_recs); - abort(); - } - - static inline void __cdecl invalid_parameter_handler(const wchar_t *, - const wchar_t *, - const wchar_t *, - unsigned int, - uintptr_t) { - crash_handler(signal_skip_recs); - abort(); - } - - NOINLINE static LONG WINAPI crash_handler(EXCEPTION_POINTERS *info) { - // The exception info supplies a trace from exactly where the issue was, - // no need to skip records - crash_handler(0, info->ContextRecord); - return EXCEPTION_CONTINUE_SEARCH; - } - - NOINLINE static void crash_handler(int skip, CONTEXT *ct = nullptr) { - - if (ct == nullptr) { - RtlCaptureContext(ctx()); - } else { - memcpy(ctx(), ct, sizeof(CONTEXT)); - } - DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), - GetCurrentProcess(), &thread_handle(), 0, FALSE, - DUPLICATE_SAME_ACCESS); - - skip_recs() = skip; - - { - std::unique_lock lk(mtx()); - crashed() = crash_status::crashed; - } - - cv().notify_one(); - - { - std::unique_lock lk(mtx()); - cv().wait(lk, [] { return crashed() != crash_status::crashed; }); - } - } - - static void handle_stacktrace(int skip_frames = 0) { - // printer creates the TraceResolver, which can supply us a machine type - // for stack walking. Without this, StackTrace can only guess using some - // macros. - // StackTrace also requires that the PDBs are already loaded, which is done - // in the constructor of TraceResolver - Printer printer; - - StackTrace st{p::GetCurrentArena()}; - st.set_machine_type(printer.resolver().machine_type()); - st.set_thread_handle(thread_handle()); - st.load_here(32 + skip_frames, ctx()); - st.skip_n_firsts(skip_frames); - - printer.address = true; - printer.print(st, std::cerr); - } -}; - -#endif // BACKWARD_SYSTEM_WINDOWS - -#ifdef BACKWARD_SYSTEM_UNKNOWN - -class SignalHandling { -public: - SignalHandling(const std::vector & = std::vector()) {} - bool init() { return false; } - bool loaded() { return false; } -}; - -#endif // BACKWARD_SYSTEM_UNKNOWN - -} // namespace backward - -#endif /* H_GUARD */ -// clang-format off diff --git a/Include/Pipe/Core/BitArray.h b/Include/Pipe/Core/BitArray.h index ca7dc7fb..fd29eb41 100644 --- a/Include/Pipe/Core/BitArray.h +++ b/Include/Pipe/Core/BitArray.h @@ -1,8 +1,9 @@ // Copyright 2015-2024 Piperift - All rights reserved #pragma once -#include "Pipe/Core/Platform.h" #include "PipeArrays.h" +#include "PipePlatform.h" + namespace p @@ -233,12 +234,16 @@ namespace p for (i = index + 1; i < numBits; ++i) { if (IsSet(i)) + { return i; + } } for (i = 0; i < index - 1; ++i) { if (IsSet(i)) + { return i; + } } return -1; } @@ -251,16 +256,22 @@ namespace p for (i = index - 1; i > 0; --i) { if (IsSet(i)) + { return i; + } } if (IsSet(0)) + { return 0; + } } for (i = numBits - 1; i > index; i--) { if (IsSet(i)) + { return i; + } } return -1; } diff --git a/Include/Pipe/Core/Broadcast.h b/Include/Pipe/Core/Broadcast.h index bf92b8f2..8e2235db 100644 --- a/Include/Pipe/Core/Broadcast.h +++ b/Include/Pipe/Core/Broadcast.h @@ -3,8 +3,9 @@ #include "Function.h" #include "Pipe/Core/Log.h" -#include "Pipe/Core/Platform.h" #include "Pipe/Memory/OwnPtr.h" +#include "PipePlatform.h" + // TODO: Remove this dependency #include "PipeReflect.h" @@ -12,7 +13,7 @@ namespace p { - struct PIPE_API EventHandle + struct P_API EventHandle { private: static u64 counter; @@ -20,7 +21,9 @@ namespace p static u64 NewId() { if (counter == 0) + { ++counter; + } return counter++; } diff --git a/Include/Pipe/Core/Char.h b/Include/Pipe/Core/Char.h index ca05fe0e..73a14f27 100644 --- a/Include/Pipe/Core/Char.h +++ b/Include/Pipe/Core/Char.h @@ -2,7 +2,7 @@ #pragma once -#include "Pipe/Core/Platform.h" +#include "PipePlatform.h" #include #include @@ -45,12 +45,13 @@ namespace p } }; -#define LITERAL(CharType, StringLiteral) TLiteral::Select(StringLiteral, L##StringLiteral) +#define P_LITERAL(CharType, StringLiteral) \ + TLiteral::Select(StringLiteral, L##StringLiteral) /** * TChar * Set of utility functions operating on a single character. The functions - * are specialized for AnsiChar and TChar character types. You can use the + * are specialized for AnsiChar and char character types. You can use the * typedefs FChar and FCharAnsi for convenience. */ @@ -152,7 +153,7 @@ namespace p } static inline bool IsUnderscore(CharType c) { - return c == LITERAL(CharType, '_'); + return c == P_LITERAL(CharType, '_'); } static inline bool IsLinebreak(CharType c) @@ -163,7 +164,7 @@ namespace p static inline i32 StrtoI32(const CharType* str, CharType** end, i32 radix); }; - using FChar = TCharHelpers; + using FChar = TCharHelpers; using FCharWide = TCharHelpers; using FCharAnsi = TCharHelpers; diff --git a/Include/Pipe/Core/Checks.h b/Include/Pipe/Core/Checks.h index a9efc34e..3383c594 100644 --- a/Include/Pipe/Core/Checks.h +++ b/Include/Pipe/Core/Checks.h @@ -2,13 +2,13 @@ #pragma once -#include "Pipe/Core/Platform.h" #include "Pipe/Core/STDFormat.h" +#include "PipePlatform.h" namespace p::details { - PIPE_API void FailedCheckError( + P_API void FailedCheckError( const AnsiChar* expr, const AnsiChar* file, u32 line, const char* text); inline void FailedCheckError( @@ -31,7 +31,7 @@ namespace p::details return false; \ }()) && ([capture]() { \ p::details::FailedCheckError(#expression, __FILE__, __LINE__, text); \ - P_DEBUG_PLATFORM_BREAK(); \ + p::PlatformDebugBreak(); \ return false; \ }())) @@ -47,7 +47,7 @@ namespace p::details if (!(expression)) [[unlikely]] \ { \ p::details::FailedCheckError(#expression, __FILE__, __LINE__, text); \ - P_DEBUG_PLATFORM_BREAK(); \ + p::PlatformDebugBreak(); \ } #endif diff --git a/Include/Pipe/Core/FixedString.h b/Include/Pipe/Core/FixedString.h index ebdb4cdc..daebc0b2 100644 --- a/Include/Pipe/Core/FixedString.h +++ b/Include/Pipe/Core/FixedString.h @@ -49,7 +49,7 @@ namespace p } // namespace Details - template> + template> struct TFixedString { // exposition only @@ -696,14 +696,14 @@ namespace p #endif // CPP20_SPACESHIP_OPERATOR_PRESENT - template + template TFixedString(const CharType (&)[N]) -> TFixedString; template - using FixedString = TFixedString; + using FixedString = TFixedString; // template - // FixedString(const TChar (&)[N]) -> FixedString; + // FixedString(const char (&)[N]) -> FixedString; template diff --git a/Include/Pipe/Core/GenericPlatform.h b/Include/Pipe/Core/GenericPlatform.h deleted file mode 100644 index 8e4e4461..00000000 --- a/Include/Pipe/Core/GenericPlatform.h +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Export.h" - -#include - - -namespace p -{ - //--------------------------------------------------------------------- - // Utility for automatically setting up the pointer-sized integer type - //--------------------------------------------------------------------- - - template - struct SelectIntPointerType - { - // nothing here are is it an error if the partial specializations fail - }; - - template - struct SelectIntPointerType - { - typedef T64BITS TIntPointer; // select the 64 bit type - }; - - template - struct SelectIntPointerType - { - using TIntPointer = T32BITS; // select the 32 bit type - }; - - - /** - * Generic types for almost all compilers and platforms - */ - struct PIPE_API GenericPlatformTypes - { - // Character types. - // An ANSI character - 8-bit fixed-width representation of 7-bit characters. - using AnsiChar = char; - - // A wide character - In-memory only. ?-bit fixed-width representation of - // the platform's natural wide character set. Could be different sizes on - // different platforms. - using WideChar = wchar_t; - - // An 8-bit character type - In-memory only. 8-bit representation. Should - // really be char8_t but making this the generic option is easier for - // compilers which don't fully support C++11 yet (i.e. MSVC). - using Char8 = unsigned char; - - // A 16-bit character type - In-memory only. 16-bit representation. Should - // really be char16_t but making this the generic option is easier for - // compilers which don't fully support C++11 yet (i.e. MSVC). - using Char16 = unsigned short int; - - // A 32-bit character type - In-memory only. 32-bit representation. Should - // really be char32_t but making this the generic option is easier for - // compilers which don't fully support C++11 yet (i.e. MSVC). - using Char32 = unsigned int; - - // A switchable character - In-memory only. - // Either AnsiChar or WideChar - using TChar = AnsiChar; - - // unsigned int the same size as a pointer - using uPtr = SelectIntPointerType::TIntPointer; - - // signed int the same size as a pointer - using iPtr = SelectIntPointerType::TIntPointer; - - // unsigned int the same size as a pointer - using sizet = uPtr; - - // signed int the same size as a pointer - using ssizet = iPtr; - - using TYPE_OF_NULL = std::int32_t; - using TYPE_OF_NULLPTR = decltype(nullptr); - }; -} // namespace p - - -#ifndef PLATFORM_TCHAR_IS_WCHAR - #define PLATFORM_TCHAR_IS_WCHAR 0 -#endif -#ifndef PLATFORM_TCHAR_IS_CHAR8 - #define PLATFORM_TCHAR_IS_CHAR8 0 -#endif -#ifndef PLATFORM_TCHAR_IS_CHAR16 - #define PLATFORM_TCHAR_IS_CHAR16 0 -#endif -#ifndef PLATFORM_TCHAR_IS_CHAR32 - #define PLATFORM_TCHAR_IS_CHAR32 0 -#endif - - -#ifndef P_LIKELY - #if (defined(__clang__) || defined(__GNUC__)) && (P_PLATFORM_LINUX) - #define P_LIKELY(x) __builtin_expect(!!(x), 1) - #else - #define P_LIKELY(x) (x) - #endif -#endif - -#ifndef P_UNLIKELY - #if (defined(__clang__) || defined(__GNUC__)) && (P_PLATFORM_LINUX) - #define P_UNLIKELY(x) __builtin_expect(!!(x), 0) - #else - #define P_UNLIKELY(x) (x) - #endif -#endif diff --git a/Include/Pipe/Core/GenericPlatformMisc.h b/Include/Pipe/Core/GenericPlatformMisc.h deleted file mode 100644 index e01904fe..00000000 --- a/Include/Pipe/Core/GenericPlatformMisc.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/Platform.h" -#include "Pipe/Export.h" - - -namespace p -{ - struct Guid; - - /** - * Generic types for almost all compilers and platforms - */ - struct PIPE_API GenericPlatformMisc - { - static void CreateGuid(Guid& guid); - - static u64 GetCycles64(); - - static u32 GetCycles() - { - return u32(GetCycles64()); - } - }; -} // namespace p diff --git a/Include/Pipe/Core/Guid.h b/Include/Pipe/Core/Guid.h index f791acab..d2fcffbb 100644 --- a/Include/Pipe/Core/Guid.h +++ b/Include/Pipe/Core/Guid.h @@ -61,7 +61,7 @@ namespace p /** * Implements a globally unique identifier. */ - struct PIPE_API Guid + struct P_API Guid { public: /** Default constructor. */ @@ -194,7 +194,7 @@ namespace p return Value.ToString(); } - friend void LexFromString(Guid& Result, const TChar* String) + friend void LexFromString(Guid& Result, const char* String) { Guid::Parse(String, Result); } diff --git a/Include/Pipe/Core/Hash.h b/Include/Pipe/Core/Hash.h index 17e16074..9e6c621b 100644 --- a/Include/Pipe/Core/Hash.h +++ b/Include/Pipe/Core/Hash.h @@ -2,23 +2,23 @@ #pragma once -#include "Pipe/Core/Platform.h" #include "Pipe/Core/TypeTraits.h" +#include "PipePlatform.h" #include namespace p { - PIPE_API sizet HashBytes(void const* ptr, sizet const len); - PIPE_API sizet HashInt(u64 x); + P_API sizet HashBytes(void const* ptr, sizet const len); + P_API sizet HashInt(u64 x); // FNV String hash // Use offset and prime based on the architecture (64bit or 32bit) // http://www.isthe.com/chongo/tech/comp/fnv/index.html - template - inline PIPE_API constexpr sizet GetStringHash(const CharType* str) + template + inline P_API constexpr sizet GetStringHash(const CharType* str) { // 32/64 bit architecture switch if constexpr (sizeof(sizet) < 64) @@ -41,8 +41,8 @@ namespace p } } - template - inline PIPE_API constexpr sizet GetStringHash(const CharType* str, sizet size) + template + inline P_API constexpr sizet GetStringHash(const CharType* str, sizet size) { // 32/64 bit architecture switch if constexpr (sizeof(sizet) < 64) @@ -97,7 +97,7 @@ namespace p template concept Hashable = requires(T a) { - { GetHash(a) } -> std::convertible_to; + { GetHash(a) } -> std::convertible_to; }; // clang-format on } // namespace p diff --git a/Include/Pipe/Core/Limits.h b/Include/Pipe/Core/Limits.h index f3f2e007..3155a0e8 100644 --- a/Include/Pipe/Core/Limits.h +++ b/Include/Pipe/Core/Limits.h @@ -1,7 +1,7 @@ // Copyright 2015-2024 Piperift - All rights reserved #pragma once -#include "Pipe/Core/Platform.h" +#include "PipePlatform.h" #include diff --git a/Include/Pipe/Core/LinuxPlatform.h b/Include/Pipe/Core/LinuxPlatform.h deleted file mode 100644 index a2953f5b..00000000 --- a/Include/Pipe/Core/LinuxPlatform.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/GenericPlatform.h" - -#define P_PLATFORM_LINUX_USE_CHAR16 0 - - -namespace p -{ - /** - * Linux specific types - **/ - struct LinuxPlatformTypes : public GenericPlatformTypes - { - using DWORD = unsigned int; - using sizet = __SIZE_TYPE__; - using TYPE_OF_NULL = decltype(__null); -#if P_PLATFORM_LINUX_USE_CHAR16 - using WideChar = char16_t; - using TChar = WideChar; -#endif - }; - - using PlatformTypes = LinuxPlatformTypes; -} // namespace p - - -#if P_DEBUG - #define P_FORCEINLINE inline -#else - #define P_FORCEINLINE inline __attribute__((always_inline)) -#endif // P_DEBUG -#define P_NOINLINE __attribute__((noinline)) - -#define P_PLATFORM_BREAK() __asm__ volatile("int $0x03") - -#if (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 6)) - #define DISABLE_OPTIMIZATION_ACTUAL _Pragma("clang optimize off") - #define ENABLE_OPTIMIZATION_ACTUAL _Pragma("clang optimize on") -#endif - -#if P_PLATFORM_LINUX_USE_CHAR16 - #undef PLATFORM_TCHAR_IS_CHAR16 - #define PLATFORM_TCHAR_IS_CHAR16 1 -#endif diff --git a/Include/Pipe/Core/LinuxPlatformMisc.h b/Include/Pipe/Core/LinuxPlatformMisc.h deleted file mode 100644 index af3a0dcf..00000000 --- a/Include/Pipe/Core/LinuxPlatformMisc.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/GenericPlatformMisc.h" - - -namespace p -{ - struct LinuxPlatformMisc : public GenericPlatformMisc - {}; - - using PlatformMisc = LinuxPlatformMisc; -} // namespace p diff --git a/Include/Pipe/Core/Log.h b/Include/Pipe/Core/Log.h index 97a50d55..7adaefc4 100644 --- a/Include/Pipe/Core/Log.h +++ b/Include/Pipe/Core/Log.h @@ -9,12 +9,19 @@ namespace p { - PIPE_API void InitLog(StringView logPath = {}); - PIPE_API void ShutdownLog(); + struct Logger + { + std::function infoCallback; + std::function warningCallback; + std::function errorCallback; + }; + + P_API void InitLog(Logger* logger = nullptr); + P_API void ShutdownLog(); - PIPE_API void Info(StringView msg); - PIPE_API void Warning(StringView msg); - PIPE_API void Error(StringView msg); + P_API void Info(StringView msg); + P_API void Warning(StringView msg); + P_API void Error(StringView msg); template void Info(StringView format, Args... args) diff --git a/Include/Pipe/Core/MacPlatform.h b/Include/Pipe/Core/MacPlatform.h deleted file mode 100644 index 34a4ab6c..00000000 --- a/Include/Pipe/Core/MacPlatform.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/GenericPlatform.h" - -#include - - -#define P_PLATFORM_MACOS_USE_CHAR16 0 - - -namespace p -{ - /** - * Mac specific types - **/ - struct MacPlatformTypes : public GenericPlatformTypes - { - using DWORD = unsigned int; - using sizet = std::size_t; - using TYPE_OF_NULL = decltype(nullptr); -#if P_PLATFORM_MACOS_USE_CHAR16 - using WideChar = char16_t; - using TChar = WideChar; -#else - using Char16 = char16_t; -#endif - }; - - using PlatformTypes = MacPlatformTypes; -} // namespace p - -#if __is_target_arch(arm64) || __is_target_arch(arm64e) - #define P_PLATFORM_MACOS_ARM64 1 - #define P_PLATFORM_MACOS_X86 0 -#else - #define P_PLATFORM_MACOS_ARM64 0 - #define P_PLATFORM_MACOS_X86 1 -#endif - - -#if P_DEBUG - #define P_FORCEINLINE inline /* Don't force code to be inline */ -#else - #define P_FORCEINLINE inline __attribute__((always_inline)) /* Force code to be inline */ -#endif -#define P_NOINLINE __attribute__((noinline)) - -#if P_PLATFORM_MACOS_X86 - #define P_PLATFORM_BREAK() __asm__("int $3") -#else - #define P_PLATFORM_BREAK() __builtin_debugtrap() -#endif - - -#if (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 6)) - #define DISABLE_OPTIMIZATION_ACTUAL _Pragma("clang optimize off") - #define ENABLE_OPTIMIZATION_ACTUAL _Pragma("clang optimize on") -#endif - -#if P_PLATFORM_MACOS_USE_CHAR16 - #undef PLATFORM_TCHAR_IS_CHAR16 - #define PLATFORM_TCHAR_IS_CHAR16 1 -#endif diff --git a/Include/Pipe/Core/MacPlatformMisc.h b/Include/Pipe/Core/MacPlatformMisc.h deleted file mode 100644 index b27ce101..00000000 --- a/Include/Pipe/Core/MacPlatformMisc.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/GenericPlatformMisc.h" - - -namespace p -{ - struct MacPlatformMisc : public GenericPlatformMisc - {}; - - using PlatformMisc = MacPlatformMisc; -} // namespace p diff --git a/Include/Pipe/Core/Map.h b/Include/Pipe/Core/Map.h index 166ab85b..866456a6 100644 --- a/Include/Pipe/Core/Map.h +++ b/Include/Pipe/Core/Map.h @@ -4,12 +4,11 @@ #include "Pipe/Core/FixedString.h" #include "Pipe/Core/Hash.h" -#include "Pipe/Core/Platform.h" #include "Pipe/Core/Templates.h" #include "Pipe/Core/Utility.h" #include "Pipe/Extern/sparse_map.h" -#include "Pipe/Memory/Alloc.h" -#include "Pipe/Memory/STLAllocator.h" +#include "PipeMemory.h" +#include "PipePlatform.h" #include #include @@ -17,7 +16,7 @@ namespace p { - template + template struct TMapHash { sizet operator()(const T& v) const diff --git a/Include/Pipe/Core/PageBuffer.h b/Include/Pipe/Core/PageBuffer.h index 13e8cea9..7ceff887 100644 --- a/Include/Pipe/Core/PageBuffer.h +++ b/Include/Pipe/Core/PageBuffer.h @@ -2,9 +2,9 @@ #pragma once -#include "Pipe/Core/Platform.h" -#include "Pipe/Memory/Arena.h" #include "PipeArrays.h" +#include "PipeMemory.h" +#include "PipePlatform.h" #include diff --git a/Include/Pipe/Core/Platform.h b/Include/Pipe/Core/Platform.h deleted file mode 100644 index f0cabc55..00000000 --- a/Include/Pipe/Core/Platform.h +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved - -#pragma once - -#ifndef P_PLATFORM_WINDOWS - #if defined(_WIN64) || defined(_WIN32) - #define P_PLATFORM_WINDOWS 1 - #else - #define P_PLATFORM_WINDOWS 0 - #endif -#endif -#ifndef P_PLATFORM_LINUX - #if defined(__linux__) - #define P_PLATFORM_LINUX 1 - #if defined(__ANDROID__) || defined(ANDROID) - #define P_PLATFORM_ANDROID 1 - #endif - #else - #define P_PLATFORM_LINUX 0 - #define P_PLATFORM_ANDROID 0 - #endif -#endif -#ifndef P_PLATFORM_APPLE - #if defined(__APPLE__) || defined(__APPLE_CC__) - #define P_PLATFORM_APPLE 1 - #if defined(__IPHONE__) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) \ - || (defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR) - #define P_PLATFORM_IOS 1 - #define P_PLATFORM_MACOS 0 - #else - #define P_PLATFORM_MACOS 1 - #define P_PLATFORM_IOS 0 - #endif - #else - #define P_PLATFORM_APPLE 0 - #define P_PLATFORM_MACOS 0 - #define P_PLATFORM_IOS 0 - #endif -#endif - -#ifndef P_PLATFORM_BSD - #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \ - || defined(__DragonFly__) - #define P_PLATFORM_BSD 1 - #else - #define P_PLATFORM_BSD 0 - #endif -#endif - -#if P_PLATFORM_WINDOWS - #include "Pipe/Core/WindowsPlatform.h" -#elif P_PLATFORM_LINUX - #include "Pipe/Core/LinuxPlatform.h" -#elif P_PLATFORM_MACOS - #include "Pipe/Core/MacPlatform.h" -#else - #error Unknown platform -#endif - - -namespace p -{ - ///////////////////////////////////////////////////////////// - // Define platform types as global types - - // Unsigned base types - // An 8-bit unsigned integer - using u8 = std::uint8_t; - // A 16-bit unsigned integer - using u16 = std::uint16_t; - // A 32-bit unsigned integer - using u32 = std::uint32_t; - // A 64-bit unsigned integer - using u64 = std::uint64_t; - - // Signed base types - // An 8-bit signed integer - using i8 = std::int8_t; - /// A 16-bit signed integer - using i16 = std::int16_t; - /// A 32-bit signed integer - using i32 = std::int32_t; - /// A 64-bit signed integer - using i64 = std::int64_t; - /// A 32-bit floating point number - using f32 = float; - /// A 64-bit floating point number - using f64 = double; - - // Character types - // An ANSI character. Normally a signed type. - using AnsiChar = PlatformTypes::AnsiChar; - // A wide character. Normally a signed type. - using WideChar = PlatformTypes::WideChar; - // Either AnsiChar or WideChar, depending on whether the platform supports wide characters - using TChar = PlatformTypes::TChar; - // An 8-bit character containing a UTF8 (Unicode, 8-bit, variable-width) code unit. - using Char8 = PlatformTypes::Char8; - // A 16-bit character containing a UTF16 (Unicode, 16-bit, variable-width) code unit. - using Char16 = PlatformTypes::Char16; - // A 32-bit character containing a UTF32 (Unicode, 32-bit, fixed-width) code unit. - using Char32 = PlatformTypes::Char32; - - - // Pointer types - - // An unsigned integer the same size as a pointer - using uPtr = PlatformTypes::uPtr; - // A signed integer the same size as a pointer - using iPtr = PlatformTypes::iPtr; - // An unsigned integer the same size as a pointer, the same as UPTRINT - using sizet = PlatformTypes::sizet; - // An integer the same size as a pointer, the same as PTRINT - using ssizet = PlatformTypes::ssizet; - - // The type of the NULL constant. - using TYPE_OF_NULL = PlatformTypes::TYPE_OF_NULL; - // The type of the C++ nullptr keyword. - using TYPE_OF_NULLPTR = PlatformTypes::TYPE_OF_NULLPTR; - - - constexpr i32 NO_INDEX = -1; -} // namespace p - - -#if !defined(TX) - #if PLATFORM_TCHAR_IS_WCHAR - #define TX(x) L##x - #elif PLATFORM_TCHAR_IS_CHAR8 - #define TX(x) x - #elif PLATFORM_TCHAR_IS_CHAR16 - #define TX(x) u##x - #elif PLATFORM_TCHAR_IS_CHAR32 - #define TX(x) U##x - #else - #define TX(x) x - #endif -#endif - -#undef UNIQUE_FUNCTION_ID -#if defined(_MSC_VER) - #define UNIQUE_FUNCTION_ID __FUNCSIG__ -#else - #if defined(__GNUG__) - #define UNIQUE_FUNCTION_ID __PRETTY_FUNCTION__ - #endif -#endif - -#if P_RELEASE - #define P_DEBUG_PLATFORM_BREAK() -#else - #define P_DEBUG_PLATFORM_BREAK() P_PLATFORM_BREAK() -#endif - -#define DISABLE_OPTIMIZATION DISABLE_OPTIMIZATION_ACTUAL -#if P_DEBUG - #define ENABLE_OPTIMIZATION DISABLE_OPTIMIZATION_ACTUAL -#else - #define ENABLE_OPTIMIZATION ENABLE_OPTIMIZATION_ACTUAL -#endif - -#ifndef P_FORCEINLINE - #if defined(_MSC_VER) - #define P_FORCEINLINE __forceinline - #else - #define P_FORCEINLINE inline __attribute__((always_inline)) - #endif -#endif - -#if defined(__cplusplus) && (__cplusplus >= 201703) - #define P_NODISCARD [[nodiscard]] -#elif (defined(__GNUC__) && (__GNUC__ >= 4)) \ - || defined(__clang__) // includes clang, icc, and clang-cl - #define P_NODISCARD __attribute__((warn_unused_result)) -#elif defined(_HAS_NODISCARD) - #define P_NODISCARD _NODISCARD -#elif (_MSC_VER >= 1700) - #define P_NODISCARD _Check_return_ -#else - #define P_NODISCARD -#endif - -#if defined(_MSC_VER) || defined(__MINGW32__) - #if defined(__MINGW32__) - #define P_RESTRICT - #define P_ATTR_MALLOC __attribute__((malloc)) - #else - #if (_MSC_VER >= 1900) && !defined(__EDG__) - #define P_RESTRICT __declspec(allocator) __declspec(restrict) - #else - #define P_RESTRICT __declspec(restrict) - #endif - #define P_ATTR_MALLOC - #endif -#elif defined(__GNUC__) // includes clang and icc - #define P_RESTRICT - #define P_ATTR_MALLOC __attribute__((malloc)) -#else - #define P_RESTRICT - #define P_ATTR_MALLOC -#endif diff --git a/Include/Pipe/Core/PlatformMisc.h b/Include/Pipe/Core/PlatformMisc.h deleted file mode 100644 index 2d029a36..00000000 --- a/Include/Pipe/Core/PlatformMisc.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved - -#pragma once - -#if P_PLATFORM_WINDOWS - #include "Pipe/Core/WindowsPlatformMisc.h" -#elif P_PLATFORM_LINUX - #include "Pipe/Core/LinuxPlatformMisc.h" -#elif P_PLATFORM_MACOS - #include "Pipe/Core/MacPlatformMisc.h" -#else - #error Unknown platform -#endif diff --git a/Include/Pipe/Core/Set.h b/Include/Pipe/Core/Set.h index 4b6ea6f7..20b00d91 100644 --- a/Include/Pipe/Core/Set.h +++ b/Include/Pipe/Core/Set.h @@ -3,10 +3,10 @@ #pragma once #include "Pipe/Core/Hash.h" -#include "Pipe/Core/Platform.h" #include "Pipe/Core/Utility.h" #include "Pipe/Extern/sparse_set.h" -#include "Pipe/Memory/STLAllocator.h" +#include "PipeMemory.h" +#include "PipePlatform.h" #include #include diff --git a/Include/Pipe/Core/String.h b/Include/Pipe/Core/String.h index 9c2bedc5..91341fc7 100644 --- a/Include/Pipe/Core/String.h +++ b/Include/Pipe/Core/String.h @@ -3,13 +3,13 @@ #pragma once #include "Pipe/Core/Hash.h" -#include "Pipe/Core/Platform.h" #include "Pipe/Core/STDFormat.h" #include "Pipe/Core/StringView.h" #include "Pipe/Core/Utility.h" -#include "Pipe/Extern/utf8.h" -#include "Pipe/Memory/STLAllocator.h" +#include "Pipe/Extern/utf8/unchecked.h" #include "PipeArrays.h" +#include "PipeMemory.h" +#include "PipePlatform.h" #include "PipeSerializeFwd.h" #include @@ -24,11 +24,11 @@ namespace p template using TString = std::basic_string, std::allocator>; - using String = TString; + using String = TString; using WString = TString; template - using FormatString = std::basic_format_string; + using FormatString = std::basic_format_string; namespace Strings { @@ -76,7 +76,7 @@ namespace p return str; } - PIPE_API String ToSentenceCase(StringView value); + P_API String ToSentenceCase(StringView value); /** * Breaks up a delimited string into elements of a string array. @@ -87,23 +87,23 @@ namespace p * * @return The number of elements in InArray */ - PIPE_API i32 ParseIntoArray(const String& str, TArray& OutArray, - const TChar* pchDelim, bool InCullEmpty = true); + P_API i32 ParseIntoArray(const String& str, TArray& OutArray, const char* pchDelim, + bool InCullEmpty = true); - PIPE_API void RemoveFromStart(String& str, sizet size); - PIPE_API void RemoveFromEnd(String& str, sizet size); - PIPE_API void RemoveFromEnd(String& str, StringView subStr); + P_API void RemoveFromStart(String& str, sizet size); + P_API void RemoveFromEnd(String& str, sizet size); + P_API void RemoveFromEnd(String& str, StringView subStr); - PIPE_API bool RemoveCharFromEnd(String& str, TChar c); + P_API bool RemoveCharFromEnd(String& str, char c); - PIPE_API i32 Split(const String& str, TArray& tokens, const TChar delim); + P_API i32 Split(const String& str, TArray& tokens, const char delim); - PIPE_API bool Split(const String& str, String& a, String& b, const TChar* delim); + P_API bool Split(const String& str, String& a, String& b, const char* delim); - PIPE_API bool IsNumeric(const String& str); - PIPE_API bool IsNumeric(const TChar* Str); + P_API bool IsNumeric(const String& str); + P_API bool IsNumeric(const char* Str); - PIPE_API String ParseMemorySize(sizet size); + P_API String ParseMemorySize(sizet size); template inline void ConvertTo(TStringView source, ToStringType& dest) @@ -120,19 +120,19 @@ namespace p } else if constexpr (sizeof(FromChar) == 1 && sizeof(ToChar) == 2) { - utf8::utf8to16(source.begin(), source.end(), std::back_inserter(dest)); + utf8::unchecked::utf8to16(source.begin(), source.end(), std::back_inserter(dest)); } else if constexpr (sizeof(FromChar) == 2 && sizeof(ToChar) == 1) { - utf8::utf16to8(source.begin(), source.end(), std::back_inserter(dest)); + utf8::unchecked::utf16to8(source.begin(), source.end(), std::back_inserter(dest)); } else if constexpr (sizeof(FromChar) == 1 && sizeof(ToChar) == 4) { - utf8::utf8to32(source.begin(), source.end(), std::back_inserter(dest)); + utf8::unchecked::utf8to32(source.begin(), source.end(), std::back_inserter(dest)); } else if constexpr (sizeof(FromChar) == 4 && sizeof(ToChar) == 1) { - utf8::utf32to8(source.begin(), source.end(), std::back_inserter(dest)); + utf8::unchecked::utf32to8(source.begin(), source.end(), std::back_inserter(dest)); } else { diff --git a/Include/Pipe/Core/StringView.h b/Include/Pipe/Core/StringView.h index d15b892a..daf6de69 100644 --- a/Include/Pipe/Core/StringView.h +++ b/Include/Pipe/Core/StringView.h @@ -4,7 +4,7 @@ #include "Pipe/Core/Hash.h" #include "Pipe/Core/Optional.h" -#include "Pipe/Core/Platform.h" +#include "PipePlatform.h" #include #include @@ -15,7 +15,7 @@ namespace p template using TStringView = std::basic_string_view>; - using StringView = TStringView; + using StringView = TStringView; using WStringView = TStringView; @@ -187,139 +187,139 @@ namespace p // END Any Char API - // BEGIN TChar API - // Specializations to avoid requiring on function calls + // BEGIN char API + // Specializations to avoid requiring on function calls - PIPE_API constexpr sizet Find( - const StringView str, const TChar c, FindDirection direction = FindDirection::Front) + P_API constexpr sizet Find( + const StringView str, const char c, FindDirection direction = FindDirection::Front) { - return Find(str, c, direction); + return Find(str, c, direction); } - PIPE_API constexpr sizet Find(const StringView str, const StringView subStr, + P_API constexpr sizet Find(const StringView str, const StringView subStr, FindDirection direction = FindDirection::Front) { - return Find(str, subStr, direction); + return Find(str, subStr, direction); } - PIPE_API constexpr bool Contains( - const StringView str, const TChar c, FindDirection direction = FindDirection::Front) + P_API constexpr bool Contains( + const StringView str, const char c, FindDirection direction = FindDirection::Front) { - return Contains(str, c, direction); + return Contains(str, c, direction); } - PIPE_API constexpr bool Contains(const StringView str, const StringView subStr, + P_API constexpr bool Contains(const StringView str, const StringView subStr, FindDirection direction = FindDirection::Front) { - return Contains(str, subStr, direction); + return Contains(str, subStr, direction); } - PIPE_API constexpr bool Equals(const StringView str, const StringView other) + P_API constexpr bool Equals(const StringView str, const StringView other) { - return Equals(str, other); + return Equals(str, other); } - PIPE_API constexpr bool Equals(const StringView str, const TChar c) + P_API constexpr bool Equals(const StringView str, const char c) { - return Equals(str, c); + return Equals(str, c); } - PIPE_API constexpr bool StartsWith(const StringView str, const StringView subStr) + P_API constexpr bool StartsWith(const StringView str, const StringView subStr) { - return StartsWith(str, subStr); + return StartsWith(str, subStr); } - PIPE_API constexpr bool EndsWith(const StringView str, const StringView subStr) + P_API constexpr bool EndsWith(const StringView str, const StringView subStr) { - return EndsWith(str, subStr); + return EndsWith(str, subStr); } - PIPE_API constexpr bool EndsWith(const StringView str, const TChar c) + P_API constexpr bool EndsWith(const StringView str, const char c) { - return EndsWith(str, c); + return EndsWith(str, c); } - PIPE_API constexpr StringView RemoveFromStart(const StringView str, sizet size) + P_API constexpr StringView RemoveFromStart(const StringView str, sizet size) { - return RemoveFromStart(str, size); + return RemoveFromStart(str, size); } - PIPE_API constexpr StringView RemoveFromStart(const StringView str, const StringView subStr) + P_API constexpr StringView RemoveFromStart(const StringView str, const StringView subStr) { - return RemoveFromStart(str, subStr); + return RemoveFromStart(str, subStr); } - PIPE_API constexpr StringView RemoveFromEnd(const StringView str, sizet size) + P_API constexpr StringView RemoveFromEnd(const StringView str, sizet size) { - return RemoveFromEnd(str, size); + return RemoveFromEnd(str, size); } - PIPE_API constexpr StringView RemoveFromEnd(const StringView str, const StringView subStr) + P_API constexpr StringView RemoveFromEnd(const StringView str, const StringView subStr) { - return RemoveFromEnd(str, subStr); + return RemoveFromEnd(str, subStr); } - PIPE_API constexpr StringView RemoveCharFromEnd(const StringView str, const TChar c) + P_API constexpr StringView RemoveCharFromEnd(const StringView str, const char c) { - return RemoveCharFromEnd(str, c); + return RemoveCharFromEnd(str, c); } - PIPE_API constexpr StringView FrontSubstr(StringView str, sizet size) + P_API constexpr StringView FrontSubstr(StringView str, sizet size) { - return FrontSubstr(str, size); + return FrontSubstr(str, size); } - PIPE_API constexpr StringView BackSubstr(StringView str, sizet size) + P_API constexpr StringView BackSubstr(StringView str, sizet size) { - return BackSubstr(str, size); + return BackSubstr(str, size); } - PIPE_API constexpr void Replace( - StringView str, const TChar searchChar, const TChar replacementChar) + P_API constexpr void Replace( + StringView str, const char searchChar, const char replacementChar) { - Replace(str, searchChar, replacementChar); + Replace(str, searchChar, replacementChar); } - PIPE_API constexpr sizet Length(const TChar* str) + P_API constexpr sizet Length(const char* str) { - return Length(str); + return Length(str); } template TOptional ToNumber(StringView str); - PIPE_API TOptional ToFloat(StringView str); - PIPE_API TOptional ToDouble(StringView str); - PIPE_API TOptional ToU32(StringView str); - PIPE_API TOptional ToI32(StringView str); - PIPE_API TOptional ToU64(StringView str); - PIPE_API TOptional ToI64(StringView str); + P_API TOptional ToFloat(StringView str); + P_API TOptional ToDouble(StringView str); + P_API TOptional ToU32(StringView str); + P_API TOptional ToI32(StringView str); + P_API TOptional ToU64(StringView str); + P_API TOptional ToI64(StringView str); /** Begin ToNumber spetializations */ template<> - PIPE_API TOptional ToNumber(StringView str); + P_API TOptional ToNumber(StringView str); template<> - PIPE_API TOptional ToNumber(StringView str); + P_API TOptional ToNumber(StringView str); template<> - PIPE_API TOptional ToNumber(StringView str); + P_API TOptional ToNumber(StringView str); template<> - PIPE_API TOptional ToNumber(StringView str); + P_API TOptional ToNumber(StringView str); template<> - PIPE_API TOptional ToNumber(StringView str); + P_API TOptional ToNumber(StringView str); template<> - PIPE_API TOptional ToNumber(StringView str); + P_API TOptional ToNumber(StringView str); template<> - PIPE_API TOptional ToNumber(StringView str); + P_API TOptional ToNumber(StringView str); template<> - PIPE_API TOptional ToNumber(StringView str); + P_API TOptional ToNumber(StringView str); template<> - PIPE_API TOptional ToNumber(StringView str); + P_API TOptional ToNumber(StringView str); template<> - PIPE_API TOptional ToNumber(StringView str); + P_API TOptional ToNumber(StringView str); /** End ToNumber spetializations */ - // END TChar API + // END char API } // namespace Strings @@ -328,7 +328,7 @@ namespace p return GetStringHash(str.data(), str.size()); }; - inline sizet GetHash(const TChar* str) + inline sizet GetHash(const char* str) { return GetStringHash(str); }; diff --git a/Include/Pipe/Core/Subprocess.h b/Include/Pipe/Core/Subprocess.h index 32e03260..b599e791 100644 --- a/Include/Pipe/Core/Subprocess.h +++ b/Include/Pipe/Core/Subprocess.h @@ -55,7 +55,7 @@ namespace p #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif - struct PIPE_API Subprocess + struct P_API Subprocess { FILE* cinFile = nullptr; FILE* coutFile = nullptr; @@ -100,7 +100,7 @@ namespace p * If `options` contains `subprocess_option_inherit_environment`, then * `environment` must be NULL. */ - PIPE_API TOptional RunProcessEx(TView command, + P_API TOptional RunProcessEx(TView command, TView environment, SubprocessOptions options = SubprocessOptions::None); /** @@ -113,7 +113,7 @@ namespace p * @param outProcess The newly created process. * @return On success zero is returned. */ - inline PIPE_API TOptional RunProcess( + inline P_API TOptional RunProcess( TView command, SubprocessOptions options = SubprocessOptions::None) { return RunProcessEx(command, {}, options); @@ -128,7 +128,7 @@ namespace p * * Joining a process will close the stdin pipe to the process. */ - PIPE_API i32 WaitProcess(Subprocess* process, i32* outReturnCode); + P_API i32 WaitProcess(Subprocess* process, i32* outReturnCode); /** * @brief Destroy a previously created process. @@ -138,7 +138,7 @@ namespace p * If the process to be destroyed had not finished execution, it may out live * the parent process. */ - PIPE_API i32 DestroyProcess(Subprocess* process); + P_API i32 DestroyProcess(Subprocess* process); /** * @brief Terminate a previously created process. @@ -148,7 +148,7 @@ namespace p * If the process to be destroyed had not finished execution, it will be * terminated (i.e killed). */ - PIPE_API i32 TerminateProcess(Subprocess* process); + P_API i32 TerminateProcess(Subprocess* process); /** * @brief Read the standard output from the child process. @@ -162,7 +162,7 @@ namespace p * execution is to use the `SubprocessOptions::EnableAsync` option in * conjuction with this method. */ - PIPE_API u32 ReadProcessCout(Subprocess* process, char* buffer, u32 size); + P_API u32 ReadProcessCout(Subprocess* process, char* buffer, u32 size); /** * @brief Read the standard error from the child process. @@ -176,14 +176,14 @@ namespace p * execution is to use the `SubprocessOptions::EnableAsync` option in * conjuction with this method. */ - PIPE_API u32 ReadProcessCerr(Subprocess* process, char* buffer, u32 size); + P_API u32 ReadProcessCerr(Subprocess* process, char* buffer, u32 size); /** * @brief Returns if the subprocess is currently still alive and executing. * @param process The process to check. * @return If the process is still alive non-zero is returned. */ - PIPE_API bool IsAlive(Subprocess* process); + P_API bool IsAlive(Subprocess* process); /** * @brief Get the standard input file for a process. @@ -193,7 +193,7 @@ namespace p * The file returned can be written to by the parent process to feed data to * the standard input of the process. */ - PIPE_API FILE* GetProcessCin(const Subprocess* process); + P_API FILE* GetProcessCin(const Subprocess* process); /** * @brief Get the standard output file for a process. @@ -203,7 +203,7 @@ namespace p * The file returned can be read from by the parent process to read data from * the standard output of the child process. */ - PIPE_API FILE* GetProcessCout(const Subprocess* process); + P_API FILE* GetProcessCout(const Subprocess* process); /** * @brief Get the standard error file for a process. @@ -217,5 +217,5 @@ namespace p * option bit set, this function will return NULL, and the GetProcessCout * function should be used for both the standard output and error combined. */ - PIPE_API FILE* GetProcessCerr(const Subprocess* process); + P_API FILE* GetProcessCerr(const Subprocess* process); } // namespace p diff --git a/Include/Pipe/Core/Tag.h b/Include/Pipe/Core/Tag.h index be29371f..cfba7c21 100644 --- a/Include/Pipe/Core/Tag.h +++ b/Include/Pipe/Core/Tag.h @@ -16,17 +16,17 @@ namespace p * Searching, comparing and other operations are way cheaper, but creating (indexing) is more * expensive. */ - struct PIPE_API Tag + struct P_API Tag { private: - sizet hash = 0; - const TChar* str = nullptr; + sizet hash = 0; + const char* str = nullptr; public: constexpr Tag() = default; Tag(StringView value); - Tag(const TChar* value) : Tag(StringView{value}) {} + Tag(const char* value) : Tag(StringView{value}) {} explicit Tag(const String& value) : Tag(StringView(value)) {} Tag(const Tag& other); @@ -35,7 +35,7 @@ namespace p Tag& operator=(Tag&& other) noexcept; ~Tag(); - const TChar* Data() const; + const char* Data() const; u32 Size() const; StringView AsString() const; diff --git a/Include/Pipe/Core/TypeId.h b/Include/Pipe/Core/TypeId.h index b1d7c4b5..1ebdb598 100644 --- a/Include/Pipe/Core/TypeId.h +++ b/Include/Pipe/Core/TypeId.h @@ -2,8 +2,9 @@ #pragma once #include "Pipe/Core/Hash.h" -#include "Pipe/Core/Platform.h" #include "Pipe/Core/Utility.h" +#include "PipePlatform.h" + #if P_DEBUG #include "Pipe/Core/StringView.h" #include "Pipe/Core/TypeName.h" @@ -15,7 +16,7 @@ namespace p { - struct PIPE_API TypeId + struct P_API TypeId { protected: u64 id; @@ -62,6 +63,10 @@ namespace p { return id >= other.id; } + constexpr operator bool() const + { + return IsValid(); + } static consteval TypeId None() { @@ -83,12 +88,10 @@ namespace p template inline consteval TypeId GetTypeId() requires(!IsConst) { - return TypeId - { - p::GetStringHash(TX(UNIQUE_FUNCTION_ID)) + return TypeId{p::GetStringHash(P_UNIQUE_FUNCTION_ID) #if P_DEBUG - , - GetTypeName() + , + GetTypeName() #endif }; } @@ -99,6 +102,30 @@ namespace p return GetTypeId>(); } + +#pragma region Casteable + struct Casteable + { + private: + mutable TypeId typeId; + + public: + TypeId GetTypeId() const + { + if (!typeId) + { + typeId = ProvideTypeId(); + } + return typeId; + } + + protected: + virtual TypeId ProvideTypeId() const = 0; + }; + + template + concept IsCasteable = Derived, Casteable, false>; +#pragma endregion Casteable } // namespace p diff --git a/Include/Pipe/Core/TypeName.h b/Include/Pipe/Core/TypeName.h index b501d28b..3b9cd54e 100644 --- a/Include/Pipe/Core/TypeName.h +++ b/Include/Pipe/Core/TypeName.h @@ -44,8 +44,10 @@ namespace p lastFoundQuotes = c; ++c; // Skip one more character for :: } - else if (*c == '<') // Stop on templates + else if (*c == '<') + { // Stop on templates break; + } ++c; } if (lastFoundQuotes) @@ -70,8 +72,10 @@ namespace p lastFoundQuotes = c; ++c; // Skip one more character for :: } - else if (*c == '<') // Stop on templates + else if (*c == '<') + { // Stop on templates break; + } ++c; } if (lastFoundQuotes) @@ -178,5 +182,5 @@ namespace p template<> \ inline consteval p::StringView p::GetFullTypeName(bool includeNamespaces) \ { \ - return TX(name); \ + return name; \ } diff --git a/Include/Pipe/Core/TypeTraits.h b/Include/Pipe/Core/TypeTraits.h index 9bfd75fa..b9d17f10 100644 --- a/Include/Pipe/Core/TypeTraits.h +++ b/Include/Pipe/Core/TypeTraits.h @@ -1,7 +1,7 @@ // Copyright 2015-2024 Piperift - All rights reserved #pragma once -#include "Pipe/Core/Platform.h" +#include "PipePlatform.h" #include #include diff --git a/Include/Pipe/Core/Utility.h b/Include/Pipe/Core/Utility.h index b92db336..d2bbee3e 100644 --- a/Include/Pipe/Core/Utility.h +++ b/Include/Pipe/Core/Utility.h @@ -9,12 +9,6 @@ namespace p { - template - constexpr void Swap(T& a, T& b) noexcept - { - std::swap(a, b); - } - // Forward arg as movable template constexpr RemoveReference&& Move(T&& arg) noexcept @@ -39,7 +33,7 @@ namespace p template constexpr T Exchange(T& value, OtherT&& newValue) noexcept( - IsMoveConstructible&& IsAssignable) + IsMoveConstructible && IsAssignable) { // assign _New_val to _Val, return previous _Val T oldValue = p::Forward(value); @@ -47,6 +41,44 @@ namespace p return oldValue; } + template + constexpr void Swap(T& a, T& b, T& tmp) noexcept + requires(IsMoveConstructible && IsMoveAssignable) + { + tmp = Move(a); + a = Move(b); + b = Move(tmp); + } + + template + constexpr void Swap(T& a, T& b) noexcept + { + T tmp; + Swap(a, b, tmp); + } + + template + constexpr void Swap(T (&a)[Size], T (&b)[Size], T& tmp) noexcept + { + if (&a != &b) + { + T* first = a; + T* last = first + Size; + T* first2 = b; + for (; first != last; ++first, ++first2) + { + Swap(*first, *first2, tmp); + } + } + } + + template + constexpr void Swap(T (&a)[Size], T (&b)[Size]) noexcept + { + T tmp; + Swap(a, b, tmp); + } + [[noreturn]] inline void Unreachable() { #if defined(__GNUC__) diff --git a/Include/Pipe/Core/WindowsPlatform.h b/Include/Pipe/Core/WindowsPlatform.h deleted file mode 100644 index 3c985b79..00000000 --- a/Include/Pipe/Core/WindowsPlatform.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/GenericPlatform.h" - -#include - -#include - - -namespace p -{ - /** - * Windows specific types - **/ - struct PIPE_API WindowsPlatformTypes : public GenericPlatformTypes - { - using sizet = std::size_t; - }; - - using PlatformTypes = WindowsPlatformTypes; -} // namespace p - - -#define P_FORCEINLINE __forceinline /* Force code to be inline */ -#define P_NOINLINE __declspec(noinline) /* Force code to not be inlined */ - -#define P_PLATFORM_BREAK() (__nop(), __debugbreak()) - -#if !defined(__clang__) - #define DISABLE_OPTIMIZATION_ACTUAL __pragma(optimize("", off)) - #define ENABLE_OPTIMIZATION_ACTUAL __pragma(optimize("", on)) -#elif defined(_MSC_VER) // Clang only supports __pragma with -fms-extensions - #define DISABLE_OPTIMIZATION_ACTUAL __pragma(clang optimize off) - #define ENABLE_OPTIMIZATION_ACTUAL __pragma(clang optimize on) -#endif diff --git a/Include/Pipe/Core/WindowsPlatformMisc.h b/Include/Pipe/Core/WindowsPlatformMisc.h deleted file mode 100644 index 7efbf29d..00000000 --- a/Include/Pipe/Core/WindowsPlatformMisc.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/GenericPlatformMisc.h" -#include "Pipe/Core/StringView.h" - - -namespace p -{ - /** - * Windows specific types - **/ - struct PIPE_API WindowsPlatformMisc : public GenericPlatformMisc - { - static const TChar* GetSystemErrorMessage(TChar* buffer, i32 size, i32 error = 0); - }; - - using PlatformMisc = WindowsPlatformMisc; -} // namespace p diff --git a/Include/Pipe/Export.h b/Include/Pipe/Export.h new file mode 100644 index 00000000..9939b364 --- /dev/null +++ b/Include/Pipe/Export.h @@ -0,0 +1,33 @@ + +#pragma once + +#ifndef P_API + #if defined(_WIN32) + #if defined(PIPE_EXPORTS) && PIPE_EXPORTS + #define P_API __declspec(dllexport) + #elif defined(PIPE_IMPORTS) && PIPE_IMPORTS + #define P_API __declspec(dllimport) + #else + #define P_API + #endif + #elif __has_attribute(visibility) + #define P_API __attribute__((visibility("default"))) + #else + #define P_API + #endif +#endif + +#ifndef P_DEPRECATED + #define P_DEPRECATED __declspec(deprecated) +#endif + +#ifndef P_DEPRECATED_API + #define P_DEPRECATED_API P_API P_DEPRECATED +#endif + +/* NOLINTNEXTLINE(readability-avoid-unconditional-preprocessor-if) */ +#if 0 /* DEFINE_NO_DEPRECATED */ + #ifndef P_NO_DEPRECATED + #define P_NO_DEPRECATED + #endif +#endif diff --git a/Include/Pipe/Extern/fast_float.h b/Include/Pipe/Extern/fast_float.h deleted file mode 100644 index 6e7df4d4..00000000 --- a/Include/Pipe/Extern/fast_float.h +++ /dev/null @@ -1,3720 +0,0 @@ -// fast_float by Daniel Lemire -// fast_float by João Paulo Magalhaes -// -// -// with contributions from Eugene Golushkov -// with contributions from Maksim Kita -// with contributions from Marcin Wojdyr -// with contributions from Neal Richardson -// with contributions from Tim Paine -// with contributions from Fabio Pellacini -// with contributions from Lénárd Szolnoki -// with contributions from Jan Pharago -// with contributions from Maya Warrier -// with contributions from Taha Khokhar -// -// -// Licensed under the Apache License, Version 2.0, or the -// MIT License or the Boost License. This file may not be copied, -// modified, or distributed except according to those terms. -// -// MIT License Notice -// -// MIT License -// -// Copyright (c) 2021 The fast_float authors -// -// Permission is hereby granted, free of charge, to any -// person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the -// Software without restriction, including without -// limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software -// is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice -// shall be included in all copies or substantial portions -// of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -// -// Apache License (Version 2.0) Notice -// -// Copyright 2021 The fast_float authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// -// BOOST License Notice -// -// Boost Software License - Version 1.0 - August 17th, 2003 -// -// Permission is hereby granted, free of charge, to any person or organization -// obtaining a copy of the software and accompanying documentation covered by -// this license (the "Software") to use, reproduce, display, distribute, -// execute, and transmit the Software, and to prepare derivative works of the -// Software, and to permit third-parties to whom the Software is furnished to -// do so, all subject to the following: -// -// The copyright notices in the Software and this entire statement, including -// the above license grant, this restriction and the following disclaimer, -// must be included in all copies of the Software, in whole or in part, and -// all derivative works of the Software, unless such copies or derivative -// works are solely in the form of machine-executable object code generated by -// a source language processor. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -// - -#ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H -#define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H - -#ifdef __has_include -#if __has_include() -#include -#endif -#endif - -// Testing for https://wg21.link/N3652, adopted in C++14 -#if __cpp_constexpr >= 201304 -#define FASTFLOAT_CONSTEXPR14 constexpr -#else -#define FASTFLOAT_CONSTEXPR14 -#endif - -#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L -#define FASTFLOAT_HAS_BIT_CAST 1 -#else -#define FASTFLOAT_HAS_BIT_CAST 0 -#endif - -#if defined(__cpp_lib_is_constant_evaluated) && __cpp_lib_is_constant_evaluated >= 201811L -#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 -#else -#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 -#endif - -// Testing for relevant C++20 constexpr library features -#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED \ - && FASTFLOAT_HAS_BIT_CAST \ - && __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ -#define FASTFLOAT_CONSTEXPR20 constexpr -#define FASTFLOAT_IS_CONSTEXPR 1 -#else -#define FASTFLOAT_CONSTEXPR20 -#define FASTFLOAT_IS_CONSTEXPR 0 -#endif - -#endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H - -#ifndef FASTFLOAT_FLOAT_COMMON_H -#define FASTFLOAT_FLOAT_COMMON_H - -#include -#include -#include -#include -#include -#include -#ifdef __has_include - #if __has_include() && (__cplusplus > 202002L || _MSVC_LANG > 202002L) - #include - #endif -#endif - -namespace fast_float { - -#define FASTFLOAT_JSONFMT (1 << 5) -#define FASTFLOAT_FORTRANFMT (1 << 6) - -enum chars_format { - scientific = 1 << 0, - fixed = 1 << 2, - hex = 1 << 3, - no_infnan = 1 << 4, - // RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6 - json = FASTFLOAT_JSONFMT | fixed | scientific | no_infnan, - // Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed. - json_or_infnan = FASTFLOAT_JSONFMT | fixed | scientific, - fortran = FASTFLOAT_FORTRANFMT | fixed | scientific, - general = fixed | scientific -}; - -template -struct from_chars_result_t { - UC const* ptr; - std::errc ec; -}; -using from_chars_result = from_chars_result_t; - -template -struct parse_options_t { - constexpr explicit parse_options_t(chars_format fmt = chars_format::general, - UC dot = UC('.')) - : format(fmt), decimal_point(dot) {} - - /** Which number formats are accepted */ - chars_format format; - /** The character used as decimal point */ - UC decimal_point; -}; -using parse_options = parse_options_t; - -} - -#if FASTFLOAT_HAS_BIT_CAST -#include -#endif - -#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \ - || defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) \ - || defined(__MINGW64__) \ - || defined(__s390x__) \ - || (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || defined(__PPC64LE__)) \ - || defined(__loongarch64) ) -#define FASTFLOAT_64BIT 1 -#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \ - || defined(__arm__) || defined(_M_ARM) || defined(__ppc__) \ - || defined(__MINGW32__) || defined(__EMSCRIPTEN__)) -#define FASTFLOAT_32BIT 1 -#else - // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. - // We can never tell the register width, but the SIZE_MAX is a good approximation. - // UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max portability. - #if SIZE_MAX == 0xffff - #error Unknown platform (16-bit, unsupported) - #elif SIZE_MAX == 0xffffffff - #define FASTFLOAT_32BIT 1 - #elif SIZE_MAX == 0xffffffffffffffff - #define FASTFLOAT_64BIT 1 - #else - #error Unknown platform (not 32-bit, not 64-bit?) - #endif -#endif - -#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) -#include -#endif - -#if defined(_MSC_VER) && !defined(__clang__) -#define FASTFLOAT_VISUAL_STUDIO 1 -#endif - -#if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ -#define FASTFLOAT_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) -#elif defined _WIN32 -#define FASTFLOAT_IS_BIG_ENDIAN 0 -#else -#if defined(__APPLE__) || defined(__FreeBSD__) -#include -#elif defined(sun) || defined(__sun) -#include -#elif defined(__MVS__) -#include -#else -#ifdef __has_include -#if __has_include() -#include -#endif //__has_include() -#endif //__has_include -#endif -# -#ifndef __BYTE_ORDER__ -// safe choice -#define FASTFLOAT_IS_BIG_ENDIAN 0 -#endif -# -#ifndef __ORDER_LITTLE_ENDIAN__ -// safe choice -#define FASTFLOAT_IS_BIG_ENDIAN 0 -#endif -# -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -#define FASTFLOAT_IS_BIG_ENDIAN 0 -#else -#define FASTFLOAT_IS_BIG_ENDIAN 1 -#endif -#endif - -#if defined(__SSE2__) || \ - (defined(FASTFLOAT_VISUAL_STUDIO) && \ - (defined(_M_AMD64) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP == 2))) -#define FASTFLOAT_SSE2 1 -#endif - -#if defined(__aarch64__) || defined(_M_ARM64) -#define FASTFLOAT_NEON 1 -#endif - -#if defined(FASTFLOAT_SSE2) || defined(FASTFLOAT_NEON) -#define FASTFLOAT_HAS_SIMD 1 -#endif - -#if defined(__GNUC__) -// disable -Wcast-align=strict (GCC only) -#define FASTFLOAT_SIMD_DISABLE_WARNINGS \ - _Pragma("GCC diagnostic push") \ - _Pragma("GCC diagnostic ignored \"-Wcast-align\"") -#else -#define FASTFLOAT_SIMD_DISABLE_WARNINGS -#endif - -#if defined(__GNUC__) -#define FASTFLOAT_SIMD_RESTORE_WARNINGS \ - _Pragma("GCC diagnostic pop") -#else -#define FASTFLOAT_SIMD_RESTORE_WARNINGS -#endif - - - -#ifdef FASTFLOAT_VISUAL_STUDIO -#define fastfloat_really_inline __forceinline -#else -#define fastfloat_really_inline inline __attribute__((always_inline)) -#endif - -#ifndef FASTFLOAT_ASSERT -#define FASTFLOAT_ASSERT(x) { ((void)(x)); } -#endif - -#ifndef FASTFLOAT_DEBUG_ASSERT -#define FASTFLOAT_DEBUG_ASSERT(x) { ((void)(x)); } -#endif - -// rust style `try!()` macro, or `?` operator -#define FASTFLOAT_TRY(x) { if (!(x)) return false; } - -#define FASTFLOAT_ENABLE_IF(...) typename std::enable_if<(__VA_ARGS__), int>::type - - -namespace fast_float { - -fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() { -#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED - return std::is_constant_evaluated(); -#else - return false; -#endif -} - -template -fastfloat_really_inline constexpr bool is_supported_float_type() { - return std::is_same::value || std::is_same::value -#if __STDCPP_FLOAT32_T__ - || std::is_same::value -#endif -#if __STDCPP_FLOAT64_T__ - || std::is_same::value -#endif - ; -} - -template -fastfloat_really_inline constexpr bool is_supported_char_type() { - return - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value; -} - -// Compares two ASCII strings in a case insensitive manner. -template -inline FASTFLOAT_CONSTEXPR14 bool -fastfloat_strncasecmp(UC const * input1, UC const * input2, size_t length) { - char running_diff{0}; - for (size_t i = 0; i < length; ++i) { - running_diff |= (char(input1[i]) ^ char(input2[i])); - } - return (running_diff == 0) || (running_diff == 32); -} - -#ifndef FLT_EVAL_METHOD -#error "FLT_EVAL_METHOD should be defined, please include cfloat." -#endif - -// a pointer and a length to a contiguous block of memory -template -struct span { - const T* ptr; - size_t length; - constexpr span(const T* _ptr, size_t _length) : ptr(_ptr), length(_length) {} - constexpr span() : ptr(nullptr), length(0) {} - - constexpr size_t len() const noexcept { - return length; - } - - FASTFLOAT_CONSTEXPR14 const T& operator[](size_t index) const noexcept { - FASTFLOAT_DEBUG_ASSERT(index < length); - return ptr[index]; - } -}; - -struct value128 { - uint64_t low; - uint64_t high; - constexpr value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} - constexpr value128() : low(0), high(0) {} -}; - -/* Helper C++14 constexpr generic implementation of leading_zeroes */ -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 -int leading_zeroes_generic(uint64_t input_num, int last_bit = 0) { - if(input_num & uint64_t(0xffffffff00000000)) { input_num >>= 32; last_bit |= 32; } - if(input_num & uint64_t( 0xffff0000)) { input_num >>= 16; last_bit |= 16; } - if(input_num & uint64_t( 0xff00)) { input_num >>= 8; last_bit |= 8; } - if(input_num & uint64_t( 0xf0)) { input_num >>= 4; last_bit |= 4; } - if(input_num & uint64_t( 0xc)) { input_num >>= 2; last_bit |= 2; } - if(input_num & uint64_t( 0x2)) { /* input_num >>= 1; */ last_bit |= 1; } - return 63 - last_bit; -} - -/* result might be undefined when input_num is zero */ -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -int leading_zeroes(uint64_t input_num) { - assert(input_num > 0); - if (cpp20_and_in_constexpr()) { - return leading_zeroes_generic(input_num); - } -#ifdef FASTFLOAT_VISUAL_STUDIO - #if defined(_M_X64) || defined(_M_ARM64) - unsigned long leading_zero = 0; - // Search the mask data from most significant bit (MSB) - // to least significant bit (LSB) for a set bit (1). - _BitScanReverse64(&leading_zero, input_num); - return (int)(63 - leading_zero); - #else - return leading_zeroes_generic(input_num); - #endif -#else - return __builtin_clzll(input_num); -#endif -} - -// slow emulation routine for 32-bit -fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) { - return x * (uint64_t)y; -} - -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 -uint64_t umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { - uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); - uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); - uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); - uint64_t adbc_carry = (uint64_t)(adbc < ad); - uint64_t lo = bd + (adbc << 32); - *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + - (adbc_carry << 32) + (uint64_t)(lo < bd); - return lo; -} - -#ifdef FASTFLOAT_32BIT - -// slow emulation routine for 32-bit -#if !defined(__MINGW64__) -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 -uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { - return umul128_generic(ab, cd, hi); -} -#endif // !__MINGW64__ - -#endif // FASTFLOAT_32BIT - - -// compute 64-bit a*b -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -value128 full_multiplication(uint64_t a, uint64_t b) { - if (cpp20_and_in_constexpr()) { - value128 answer; - answer.low = umul128_generic(a, b, &answer.high); - return answer; - } - value128 answer; -#if defined(_M_ARM64) && !defined(__MINGW32__) - // ARM64 has native support for 64-bit multiplications, no need to emulate - // But MinGW on ARM64 doesn't have native support for 64-bit multiplications - answer.high = __umulh(a, b); - answer.low = a * b; -#elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__)) - answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 -#elif defined(FASTFLOAT_64BIT) && defined(__SIZEOF_INT128__) - __uint128_t r = ((__uint128_t)a) * b; - answer.low = uint64_t(r); - answer.high = uint64_t(r >> 64); -#else - answer.low = umul128_generic(a, b, &answer.high); -#endif - return answer; -} - -struct adjusted_mantissa { - uint64_t mantissa{0}; - int32_t power2{0}; // a negative value indicates an invalid result - adjusted_mantissa() = default; - constexpr bool operator==(const adjusted_mantissa &o) const { - return mantissa == o.mantissa && power2 == o.power2; - } - constexpr bool operator!=(const adjusted_mantissa &o) const { - return mantissa != o.mantissa || power2 != o.power2; - } -}; - -// Bias so we can get the real exponent with an invalid adjusted_mantissa. -constexpr static int32_t invalid_am_bias = -0x8000; - -// used for binary_format_lookup_tables::max_mantissa -constexpr uint64_t constant_55555 = 5 * 5 * 5 * 5 * 5; - -template -struct binary_format_lookup_tables; - -template struct binary_format : binary_format_lookup_tables { - using equiv_uint = typename std::conditional::type; - - static inline constexpr int mantissa_explicit_bits(); - static inline constexpr int minimum_exponent(); - static inline constexpr int infinite_power(); - static inline constexpr int sign_index(); - static inline constexpr int min_exponent_fast_path(); // used when fegetround() == FE_TONEAREST - static inline constexpr int max_exponent_fast_path(); - static inline constexpr int max_exponent_round_to_even(); - static inline constexpr int min_exponent_round_to_even(); - static inline constexpr uint64_t max_mantissa_fast_path(int64_t power); - static inline constexpr uint64_t max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST - static inline constexpr int largest_power_of_ten(); - static inline constexpr int smallest_power_of_ten(); - static inline constexpr T exact_power_of_ten(int64_t power); - static inline constexpr size_t max_digits(); - static inline constexpr equiv_uint exponent_mask(); - static inline constexpr equiv_uint mantissa_mask(); - static inline constexpr equiv_uint hidden_bit_mask(); -}; - -template -struct binary_format_lookup_tables { - static constexpr double powers_of_ten[] = { - 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, - 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; - - // Largest integer value v so that (5**index * v) <= 1<<53. - // 0x10000000000000 == 1 << 53 - static constexpr uint64_t max_mantissa[] = { - 0x10000000000000, - 0x10000000000000 / 5, - 0x10000000000000 / (5 * 5), - 0x10000000000000 / (5 * 5 * 5), - 0x10000000000000 / (5 * 5 * 5 * 5), - 0x10000000000000 / (constant_55555), - 0x10000000000000 / (constant_55555 * 5), - 0x10000000000000 / (constant_55555 * 5 * 5), - 0x10000000000000 / (constant_55555 * 5 * 5 * 5), - 0x10000000000000 / (constant_55555 * 5 * 5 * 5 * 5), - 0x10000000000000 / (constant_55555 * constant_55555), - 0x10000000000000 / (constant_55555 * constant_55555 * 5), - 0x10000000000000 / (constant_55555 * constant_55555 * 5 * 5), - 0x10000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5), - 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555), - 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5), - 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5), - 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), - 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5), - 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555), - 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5), - 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5), - 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), - 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5)}; -}; - -template -constexpr double binary_format_lookup_tables::powers_of_ten[]; - -template -constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; - -template -struct binary_format_lookup_tables { - static constexpr float powers_of_ten[] = {1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, - 1e6f, 1e7f, 1e8f, 1e9f, 1e10f}; - - // Largest integer value v so that (5**index * v) <= 1<<24. - // 0x1000000 == 1<<24 - static constexpr uint64_t max_mantissa[] = { - 0x1000000, - 0x1000000 / 5, - 0x1000000 / (5 * 5), - 0x1000000 / (5 * 5 * 5), - 0x1000000 / (5 * 5 * 5 * 5), - 0x1000000 / (constant_55555), - 0x1000000 / (constant_55555 * 5), - 0x1000000 / (constant_55555 * 5 * 5), - 0x1000000 / (constant_55555 * 5 * 5 * 5), - 0x1000000 / (constant_55555 * 5 * 5 * 5 * 5), - 0x1000000 / (constant_55555 * constant_55555), - 0x1000000 / (constant_55555 * constant_55555 * 5)}; -}; - -template -constexpr float binary_format_lookup_tables::powers_of_ten[]; - -template -constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; - -template <> inline constexpr int binary_format::min_exponent_fast_path() { -#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) - return 0; -#else - return -22; -#endif -} - -template <> inline constexpr int binary_format::min_exponent_fast_path() { -#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) - return 0; -#else - return -10; -#endif -} - -template <> inline constexpr int binary_format::mantissa_explicit_bits() { - return 52; -} -template <> inline constexpr int binary_format::mantissa_explicit_bits() { - return 23; -} - -template <> inline constexpr int binary_format::max_exponent_round_to_even() { - return 23; -} - -template <> inline constexpr int binary_format::max_exponent_round_to_even() { - return 10; -} - -template <> inline constexpr int binary_format::min_exponent_round_to_even() { - return -4; -} - -template <> inline constexpr int binary_format::min_exponent_round_to_even() { - return -17; -} - -template <> inline constexpr int binary_format::minimum_exponent() { - return -1023; -} -template <> inline constexpr int binary_format::minimum_exponent() { - return -127; -} - -template <> inline constexpr int binary_format::infinite_power() { - return 0x7FF; -} -template <> inline constexpr int binary_format::infinite_power() { - return 0xFF; -} - -template <> inline constexpr int binary_format::sign_index() { return 63; } -template <> inline constexpr int binary_format::sign_index() { return 31; } - -template <> inline constexpr int binary_format::max_exponent_fast_path() { - return 22; -} -template <> inline constexpr int binary_format::max_exponent_fast_path() { - return 10; -} - -template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { - return uint64_t(2) << mantissa_explicit_bits(); -} -template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { - // caller is responsible to ensure that - // power >= 0 && power <= 22 - // - // Work around clang bug https://godbolt.org/z/zedh7rrhc - return (void)max_mantissa[0], max_mantissa[power]; -} -template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { - return uint64_t(2) << mantissa_explicit_bits(); -} -template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { - // caller is responsible to ensure that - // power >= 0 && power <= 10 - // - // Work around clang bug https://godbolt.org/z/zedh7rrhc - return (void)max_mantissa[0], max_mantissa[power]; -} - -template <> -inline constexpr double binary_format::exact_power_of_ten(int64_t power) { - // Work around clang bug https://godbolt.org/z/zedh7rrhc - return (void)powers_of_ten[0], powers_of_ten[power]; -} -template <> -inline constexpr float binary_format::exact_power_of_ten(int64_t power) { - // Work around clang bug https://godbolt.org/z/zedh7rrhc - return (void)powers_of_ten[0], powers_of_ten[power]; -} - - -template <> -inline constexpr int binary_format::largest_power_of_ten() { - return 308; -} -template <> -inline constexpr int binary_format::largest_power_of_ten() { - return 38; -} - -template <> -inline constexpr int binary_format::smallest_power_of_ten() { - return -342; -} -template <> -inline constexpr int binary_format::smallest_power_of_ten() { - return -65; -} - -template <> inline constexpr size_t binary_format::max_digits() { - return 769; -} -template <> inline constexpr size_t binary_format::max_digits() { - return 114; -} - -template <> inline constexpr binary_format::equiv_uint - binary_format::exponent_mask() { - return 0x7F800000; -} -template <> inline constexpr binary_format::equiv_uint - binary_format::exponent_mask() { - return 0x7FF0000000000000; -} - -template <> inline constexpr binary_format::equiv_uint - binary_format::mantissa_mask() { - return 0x007FFFFF; -} -template <> inline constexpr binary_format::equiv_uint - binary_format::mantissa_mask() { - return 0x000FFFFFFFFFFFFF; -} - -template <> inline constexpr binary_format::equiv_uint - binary_format::hidden_bit_mask() { - return 0x00800000; -} -template <> inline constexpr binary_format::equiv_uint - binary_format::hidden_bit_mask() { - return 0x0010000000000000; -} - -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -void to_float(bool negative, adjusted_mantissa am, T &value) { - using fastfloat_uint = typename binary_format::equiv_uint; - fastfloat_uint word = (fastfloat_uint)am.mantissa; - word |= fastfloat_uint(am.power2) << binary_format::mantissa_explicit_bits(); - word |= fastfloat_uint(negative) << binary_format::sign_index(); -#if FASTFLOAT_HAS_BIT_CAST - value = std::bit_cast(word); -#else - ::memcpy(&value, &word, sizeof(T)); -#endif -} - -#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default -template -struct space_lut { - static constexpr bool value[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; -}; - -template -constexpr bool space_lut::value[]; - -inline constexpr bool is_space(uint8_t c) { return space_lut<>::value[c]; } -#endif - -template -static constexpr uint64_t int_cmp_zeros() -{ - static_assert((sizeof(UC) == 1) || (sizeof(UC) == 2) || (sizeof(UC) == 4), "Unsupported character size"); - return (sizeof(UC) == 1) ? 0x3030303030303030 : (sizeof(UC) == 2) ? (uint64_t(UC('0')) << 48 | uint64_t(UC('0')) << 32 | uint64_t(UC('0')) << 16 | UC('0')) : (uint64_t(UC('0')) << 32 | UC('0')); -} -template -static constexpr int int_cmp_len() -{ - return sizeof(uint64_t) / sizeof(UC); -} -template -static constexpr UC const * str_const_nan() -{ - return nullptr; -} -template<> -constexpr char const * str_const_nan() -{ - return "nan"; -} -template<> -constexpr wchar_t const * str_const_nan() -{ - return L"nan"; -} -template<> -constexpr char16_t const * str_const_nan() -{ - return u"nan"; -} -template<> -constexpr char32_t const * str_const_nan() -{ - return U"nan"; -} -template -static constexpr UC const * str_const_inf() -{ - return nullptr; -} -template<> -constexpr char const * str_const_inf() -{ - return "infinity"; -} -template<> -constexpr wchar_t const * str_const_inf() -{ - return L"infinity"; -} -template<> -constexpr char16_t const * str_const_inf() -{ - return u"infinity"; -} -template<> -constexpr char32_t const * str_const_inf() -{ - return U"infinity"; -} - - -template -struct int_luts { - static constexpr uint8_t chdigit[] = { - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, - 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 255, 255, 255, 255, 255, - 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 - }; - - static constexpr size_t maxdigits_u64[] = { - 64, 41, 32, 28, 25, 23, 22, 21, - 20, 19, 18, 18, 17, 17, 16, 16, - 16, 16, 15, 15, 15, 15, 14, 14, - 14, 14, 14, 14, 14, 13, 13, 13, - 13, 13, 13 - }; - - static constexpr uint64_t min_safe_u64[] = { - 9223372036854775808ull, 12157665459056928801ull, 4611686018427387904, 7450580596923828125, 4738381338321616896, - 3909821048582988049, 9223372036854775808ull, 12157665459056928801ull, 10000000000000000000ull, 5559917313492231481, - 2218611106740436992, 8650415919381337933, 2177953337809371136, 6568408355712890625, 1152921504606846976, - 2862423051509815793, 6746640616477458432, 15181127029874798299ull, 1638400000000000000, 3243919932521508681, - 6221821273427820544, 11592836324538749809ull, 876488338465357824, 1490116119384765625, 2481152873203736576, - 4052555153018976267, 6502111422497947648, 10260628712958602189ull, 15943230000000000000ull, 787662783788549761, - 1152921504606846976, 1667889514952984961, 2386420683693101056, 3379220508056640625, 4738381338321616896 - }; -}; - -template -constexpr uint8_t int_luts::chdigit[]; - -template -constexpr size_t int_luts::maxdigits_u64[]; - -template -constexpr uint64_t int_luts::min_safe_u64[]; - -template -fastfloat_really_inline -constexpr uint8_t ch_to_digit(UC c) { return int_luts<>::chdigit[static_cast(c)]; } - -fastfloat_really_inline -constexpr size_t max_digits_u64(int base) { return int_luts<>::maxdigits_u64[base - 2]; } - -// If a u64 is exactly max_digits_u64() in length, this is -// the value below which it has definitely overflowed. -fastfloat_really_inline -constexpr uint64_t min_safe_u64(int base) { return int_luts<>::min_safe_u64[base - 2]; } - -} // namespace fast_float - -#endif - - -#ifndef FASTFLOAT_FAST_FLOAT_H -#define FASTFLOAT_FAST_FLOAT_H - - -namespace fast_float { -/** - * This function parses the character sequence [first,last) for a number. It parses floating-point numbers expecting - * a locale-indepent format equivalent to what is used by std::strtod in the default ("C") locale. - * The resulting floating-point value is the closest floating-point values (using either float or double), - * using the "round to even" convention for values that would otherwise fall right in-between two values. - * That is, we provide exact parsing according to the IEEE standard. - * - * Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the - * parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned - * `ec` contains a representative error, otherwise the default (`std::errc()`) value is stored. - * - * The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`). - * - * Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of - * the type `fast_float::chars_format`. It is a bitset value: we check whether - * `fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set - * to determine whether we allow the fixed point and scientific notation respectively. - * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. - */ -template())> -FASTFLOAT_CONSTEXPR20 -from_chars_result_t from_chars(UC const * first, UC const * last, - T &value, chars_format fmt = chars_format::general) noexcept; - -/** - * Like from_chars, but accepts an `options` argument to govern number parsing. - */ -template -FASTFLOAT_CONSTEXPR20 -from_chars_result_t from_chars_advanced(UC const * first, UC const * last, - T &value, parse_options_t options) noexcept; -/** -* from_chars for integer types. -*/ -template ())> -FASTFLOAT_CONSTEXPR20 -from_chars_result_t from_chars(UC const * first, UC const * last, T& value, int base = 10) noexcept; - -} // namespace fast_float -#endif // FASTFLOAT_FAST_FLOAT_H - -#ifndef FASTFLOAT_ASCII_NUMBER_H -#define FASTFLOAT_ASCII_NUMBER_H - -#include -#include -#include -#include -#include -#include - - -#ifdef FASTFLOAT_SSE2 -#include -#endif - -#ifdef FASTFLOAT_NEON -#include -#endif - -namespace fast_float { - -template -fastfloat_really_inline constexpr bool has_simd_opt() { -#ifdef FASTFLOAT_HAS_SIMD - return std::is_same::value; -#else - return false; -#endif -} - -// Next function can be micro-optimized, but compilers are entirely -// able to optimize it well. -template -fastfloat_really_inline constexpr bool is_integer(UC c) noexcept { - return !(c > UC('9') || c < UC('0')); -} - -fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { - return (val & 0xFF00000000000000) >> 56 - | (val & 0x00FF000000000000) >> 40 - | (val & 0x0000FF0000000000) >> 24 - | (val & 0x000000FF00000000) >> 8 - | (val & 0x00000000FF000000) << 8 - | (val & 0x0000000000FF0000) << 24 - | (val & 0x000000000000FF00) << 40 - | (val & 0x00000000000000FF) << 56; -} - -// Read 8 UC into a u64. Truncates UC if not char. -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -uint64_t read8_to_u64(const UC *chars) { - if (cpp20_and_in_constexpr() || !std::is_same::value) { - uint64_t val = 0; - for(int i = 0; i < 8; ++i) { - val |= uint64_t(uint8_t(*chars)) << (i*8); - ++chars; - } - return val; - } - uint64_t val; - ::memcpy(&val, chars, sizeof(uint64_t)); -#if FASTFLOAT_IS_BIG_ENDIAN == 1 - // Need to read as-if the number was in little-endian order. - val = byteswap(val); -#endif - return val; -} - -#ifdef FASTFLOAT_SSE2 - -fastfloat_really_inline -uint64_t simd_read8_to_u64(const __m128i data) { -FASTFLOAT_SIMD_DISABLE_WARNINGS - const __m128i packed = _mm_packus_epi16(data, data); -#ifdef FASTFLOAT_64BIT - return uint64_t(_mm_cvtsi128_si64(packed)); -#else - uint64_t value; - // Visual Studio + older versions of GCC don't support _mm_storeu_si64 - _mm_storel_epi64(reinterpret_cast<__m128i*>(&value), packed); - return value; -#endif -FASTFLOAT_SIMD_RESTORE_WARNINGS -} - -fastfloat_really_inline -uint64_t simd_read8_to_u64(const char16_t* chars) { -FASTFLOAT_SIMD_DISABLE_WARNINGS - return simd_read8_to_u64(_mm_loadu_si128(reinterpret_cast(chars))); -FASTFLOAT_SIMD_RESTORE_WARNINGS -} - -#elif defined(FASTFLOAT_NEON) - - -fastfloat_really_inline -uint64_t simd_read8_to_u64(const uint16x8_t data) { -FASTFLOAT_SIMD_DISABLE_WARNINGS - uint8x8_t utf8_packed = vmovn_u16(data); - return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0); -FASTFLOAT_SIMD_RESTORE_WARNINGS -} - -fastfloat_really_inline -uint64_t simd_read8_to_u64(const char16_t* chars) { -FASTFLOAT_SIMD_DISABLE_WARNINGS - return simd_read8_to_u64(vld1q_u16(reinterpret_cast(chars))); -FASTFLOAT_SIMD_RESTORE_WARNINGS -} - -#endif // FASTFLOAT_SSE2 - -// MSVC SFINAE is broken pre-VS2017 -#if defined(_MSC_VER) && _MSC_VER <= 1900 -template -#else -template ()) = 0> -#endif -// dummy for compile -uint64_t simd_read8_to_u64(UC const*) { - return 0; -} - - -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -void write_u64(uint8_t *chars, uint64_t val) { - if (cpp20_and_in_constexpr()) { - for(int i = 0; i < 8; ++i) { - *chars = uint8_t(val); - val >>= 8; - ++chars; - } - return; - } -#if FASTFLOAT_IS_BIG_ENDIAN == 1 - // Need to read as-if the number was in little-endian order. - val = byteswap(val); -#endif - ::memcpy(chars, &val, sizeof(uint64_t)); -} - -// credit @aqrit -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 -uint32_t parse_eight_digits_unrolled(uint64_t val) { - const uint64_t mask = 0x000000FF000000FF; - const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) - const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) - val -= 0x3030303030303030; - val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; - val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; - return uint32_t(val); -} - - -// Call this if chars are definitely 8 digits. -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -uint32_t parse_eight_digits_unrolled(UC const * chars) noexcept { - if (cpp20_and_in_constexpr() || !has_simd_opt()) { - return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay - } - return parse_eight_digits_unrolled(simd_read8_to_u64(chars)); -} - - -// credit @aqrit -fastfloat_really_inline constexpr bool is_made_of_eight_digits_fast(uint64_t val) noexcept { - return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & - 0x8080808080808080)); -} - - -#ifdef FASTFLOAT_HAS_SIMD - -// Call this if chars might not be 8 digits. -// Using this style (instead of is_made_of_eight_digits_fast() then parse_eight_digits_unrolled()) -// ensures we don't load SIMD registers twice. -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -bool simd_parse_if_eight_digits_unrolled(const char16_t* chars, uint64_t& i) noexcept { - if (cpp20_and_in_constexpr()) { - return false; - } -#ifdef FASTFLOAT_SSE2 -FASTFLOAT_SIMD_DISABLE_WARNINGS - const __m128i data = _mm_loadu_si128(reinterpret_cast(chars)); - - // (x - '0') <= 9 - // http://0x80.pl/articles/simd-parsing-int-sequences.html - const __m128i t0 = _mm_add_epi16(data, _mm_set1_epi16(32720)); - const __m128i t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759)); - - if (_mm_movemask_epi8(t1) == 0) { - i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); - return true; - } - else return false; -FASTFLOAT_SIMD_RESTORE_WARNINGS -#elif defined(FASTFLOAT_NEON) -FASTFLOAT_SIMD_DISABLE_WARNINGS - const uint16x8_t data = vld1q_u16(reinterpret_cast(chars)); - - // (x - '0') <= 9 - // http://0x80.pl/articles/simd-parsing-int-sequences.html - const uint16x8_t t0 = vsubq_u16(data, vmovq_n_u16('0')); - const uint16x8_t mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1)); - - if (vminvq_u16(mask) == 0xFFFF) { - i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); - return true; - } - else return false; -FASTFLOAT_SIMD_RESTORE_WARNINGS -#else - (void)chars; (void)i; - return false; -#endif // FASTFLOAT_SSE2 -} - -#endif // FASTFLOAT_HAS_SIMD - -// MSVC SFINAE is broken pre-VS2017 -#if defined(_MSC_VER) && _MSC_VER <= 1900 -template -#else -template ()) = 0> -#endif -// dummy for compile -bool simd_parse_if_eight_digits_unrolled(UC const*, uint64_t&) { - return 0; -} - - -template ::value) = 0> -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -void loop_parse_if_eight_digits(const UC*& p, const UC* const pend, uint64_t& i) { - if (!has_simd_opt()) { - return; - } - while ((std::distance(p, pend) >= 8) && simd_parse_if_eight_digits_unrolled(p, i)) { // in rare cases, this will overflow, but that's ok - p += 8; - } -} - -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -void loop_parse_if_eight_digits(const char*& p, const char* const pend, uint64_t& i) { - // optimizes better than parse_if_eight_digits_unrolled() for UC = char. - while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(read8_to_u64(p))) { - i = i * 100000000 + parse_eight_digits_unrolled(read8_to_u64(p)); // in rare cases, this will overflow, but that's ok - p += 8; - } -} - -template -struct parsed_number_string_t { - int64_t exponent{0}; - uint64_t mantissa{0}; - UC const * lastmatch{nullptr}; - bool negative{false}; - bool valid{false}; - bool too_many_digits{false}; - // contains the range of the significant digits - span integer{}; // non-nullable - span fraction{}; // nullable -}; - -using byte_span = span; -using parsed_number_string = parsed_number_string_t; - -// Assuming that you use no more than 19 digits, this will -// parse an ASCII string. -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -parsed_number_string_t parse_number_string(UC const *p, UC const * pend, parse_options_t options) noexcept { - chars_format const fmt = options.format; - UC const decimal_point = options.decimal_point; - - parsed_number_string_t answer; - answer.valid = false; - answer.too_many_digits = false; - answer.negative = (*p == UC('-')); -#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default - if ((*p == UC('-')) || (!(fmt & FASTFLOAT_JSONFMT) && *p == UC('+'))) { -#else - if (*p == UC('-')) { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here -#endif - ++p; - if (p == pend) { - return answer; - } - if (fmt & FASTFLOAT_JSONFMT) { - if (!is_integer(*p)) { // a sign must be followed by an integer - return answer; - } - } else { - if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot - return answer; - } - } - } - UC const * const start_digits = p; - - uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) - - while ((p != pend) && is_integer(*p)) { - // a multiplication by 10 is cheaper than an arbitrary integer - // multiplication - i = 10 * i + - uint64_t(*p - UC('0')); // might overflow, we will handle the overflow later - ++p; - } - UC const * const end_of_integer_part = p; - int64_t digit_count = int64_t(end_of_integer_part - start_digits); - answer.integer = span(start_digits, size_t(digit_count)); - if (fmt & FASTFLOAT_JSONFMT) { - // at least 1 digit in integer part, without leading zeros - if (digit_count == 0 || (start_digits[0] == UC('0') && digit_count > 1)) { - return answer; - } - } - - int64_t exponent = 0; - const bool has_decimal_point = (p != pend) && (*p == decimal_point); - if (has_decimal_point) { - ++p; - UC const * before = p; - // can occur at most twice without overflowing, but let it occur more, since - // for integers with many digits, digit parsing is the primary bottleneck. - loop_parse_if_eight_digits(p, pend, i); - - while ((p != pend) && is_integer(*p)) { - uint8_t digit = uint8_t(*p - UC('0')); - ++p; - i = i * 10 + digit; // in rare cases, this will overflow, but that's ok - } - exponent = before - p; - answer.fraction = span(before, size_t(p - before)); - digit_count -= exponent; - } - if (fmt & FASTFLOAT_JSONFMT) { - // at least 1 digit in fractional part - if (has_decimal_point && exponent == 0) { - return answer; - } - } - else if (digit_count == 0) { // we must have encountered at least one integer! - return answer; - } - int64_t exp_number = 0; // explicit exponential part - if ( ((fmt & chars_format::scientific) && - (p != pend) && - ((UC('e') == *p) || (UC('E') == *p))) - || - ((fmt & FASTFLOAT_FORTRANFMT) && - (p != pend) && - ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || (UC('D') == *p)))) { - UC const * location_of_e = p; - if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || (UC('D') == *p)) { - ++p; - } - bool neg_exp = false; - if ((p != pend) && (UC('-') == *p)) { - neg_exp = true; - ++p; - } else if ((p != pend) && (UC('+') == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) - ++p; - } - if ((p == pend) || !is_integer(*p)) { - if(!(fmt & chars_format::fixed)) { - // We are in error. - return answer; - } - // Otherwise, we will be ignoring the 'e'. - p = location_of_e; - } else { - while ((p != pend) && is_integer(*p)) { - uint8_t digit = uint8_t(*p - UC('0')); - if (exp_number < 0x10000000) { - exp_number = 10 * exp_number + digit; - } - ++p; - } - if(neg_exp) { exp_number = - exp_number; } - exponent += exp_number; - } - } else { - // If it scientific and not fixed, we have to bail out. - if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } - } - answer.lastmatch = p; - answer.valid = true; - - // If we frequently had to deal with long strings of digits, - // we could extend our code by using a 128-bit integer instead - // of a 64-bit integer. However, this is uncommon. - // - // We can deal with up to 19 digits. - if (digit_count > 19) { // this is uncommon - // It is possible that the integer had an overflow. - // We have to handle the case where we have 0.0000somenumber. - // We need to be mindful of the case where we only have zeroes... - // E.g., 0.000000000...000. - UC const * start = start_digits; - while ((start != pend) && (*start == UC('0') || *start == decimal_point)) { - if(*start == UC('0')) { digit_count --; } - start++; - } - - if (digit_count > 19) { - answer.too_many_digits = true; - // Let us start again, this time, avoiding overflows. - // We don't need to check if is_integer, since we use the - // pre-tokenized spans from above. - i = 0; - p = answer.integer.ptr; - UC const* int_end = p + answer.integer.len(); - const uint64_t minimal_nineteen_digit_integer{ 1000000000000000000 }; - while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { - i = i * 10 + uint64_t(*p - UC('0')); - ++p; - } - if (i >= minimal_nineteen_digit_integer) { // We have a big integers - exponent = end_of_integer_part - p + exp_number; - } - else { // We have a value with a fractional component. - p = answer.fraction.ptr; - UC const* frac_end = p + answer.fraction.len(); - while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { - i = i * 10 + uint64_t(*p - UC('0')); - ++p; - } - exponent = answer.fraction.ptr - p + exp_number; - } - // We have now corrected both exponent and i, to a truncated value - } - } - answer.exponent = exponent; - answer.mantissa = i; - return answer; -} - -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -from_chars_result_t parse_int_string(UC const* p, UC const* pend, T& value, int base) { - from_chars_result_t answer; - - UC const* const first = p; - - bool negative = (*p == UC('-')); - if (!std::is_signed::value && negative) { - answer.ec = std::errc::invalid_argument; - answer.ptr = first; - return answer; - } -#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default - if ((*p == UC('-')) || (*p == UC('+'))) { -#else - if (*p == UC('-')) { -#endif - ++p; - } - - UC const* const start_num = p; - - while (p!= pend && *p == UC('0')) { - ++p; - } - - const bool has_leading_zeros = p > start_num; - - UC const* const start_digits = p; - - uint64_t i = 0; - if (base == 10) { - loop_parse_if_eight_digits(p, pend, i); // use SIMD if possible - } - while (p != pend) { - uint8_t digit = ch_to_digit(*p); - if (digit >= base) { - break; - } - i = uint64_t(base) * i + digit; // might overflow, check this later - p++; - } - - size_t digit_count = size_t(p - start_digits); - - if (digit_count == 0) { - if (has_leading_zeros) { - value = 0; - answer.ec = std::errc(); - answer.ptr = p; - } - else { - answer.ec = std::errc::invalid_argument; - answer.ptr = first; - } - return answer; - } - - answer.ptr = p; - - // check u64 overflow - size_t max_digits = max_digits_u64(base); - if (digit_count > max_digits) { - answer.ec = std::errc::result_out_of_range; - return answer; - } - // this check can be eliminated for all other types, but they will all require a max_digits(base) equivalent - if (digit_count == max_digits && i < min_safe_u64(base)) { - answer.ec = std::errc::result_out_of_range; - return answer; - } - - // check other types overflow - if (!std::is_same::value) { - if (i > uint64_t(std::numeric_limits::max()) + uint64_t(negative)) { - answer.ec = std::errc::result_out_of_range; - return answer; - } - } - - if (negative) { -#ifdef FASTFLOAT_VISUAL_STUDIO -#pragma warning(push) -#pragma warning(disable: 4146) -#endif - // this weird workaround is required because: - // - converting unsigned to signed when its value is greater than signed max is UB pre-C++23. - // - reinterpret_casting (~i + 1) would work, but it is not constexpr - // this is always optimized into a neg instruction (note: T is an integer type) - value = T(-std::numeric_limits::max() - T(i - uint64_t(std::numeric_limits::max()))); -#ifdef FASTFLOAT_VISUAL_STUDIO -#pragma warning(pop) -#endif - } - else { value = T(i); } - - answer.ec = std::errc(); - return answer; -} - -} // namespace fast_float - -#endif - -#ifndef FASTFLOAT_FAST_TABLE_H -#define FASTFLOAT_FAST_TABLE_H - -#include - -namespace fast_float { - -/** - * When mapping numbers from decimal to binary, - * we go from w * 10^q to m * 2^p but we have - * 10^q = 5^q * 2^q, so effectively - * we are trying to match - * w * 2^q * 5^q to m * 2^p. Thus the powers of two - * are not a concern since they can be represented - * exactly using the binary notation, only the powers of five - * affect the binary significand. - */ - -/** - * The smallest non-zero float (binary64) is 2^-1074. - * We take as input numbers of the form w x 10^q where w < 2^64. - * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. - * However, we have that - * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074. - * Thus it is possible for a number of the form w * 10^-342 where - * w is a 64-bit value to be a non-zero floating-point number. - ********* - * Any number of form w * 10^309 where w>= 1 is going to be - * infinite in binary64 so we never need to worry about powers - * of 5 greater than 308. - */ -template -struct powers_template { - -constexpr static int smallest_power_of_five = binary_format::smallest_power_of_ten(); -constexpr static int largest_power_of_five = binary_format::largest_power_of_ten(); -constexpr static int number_of_entries = 2 * (largest_power_of_five - smallest_power_of_five + 1); -// Powers of five from 5^-342 all the way to 5^308 rounded toward one. -constexpr static uint64_t power_of_five_128[number_of_entries] = { - 0xeef453d6923bd65a,0x113faa2906a13b3f, - 0x9558b4661b6565f8,0x4ac7ca59a424c507, - 0xbaaee17fa23ebf76,0x5d79bcf00d2df649, - 0xe95a99df8ace6f53,0xf4d82c2c107973dc, - 0x91d8a02bb6c10594,0x79071b9b8a4be869, - 0xb64ec836a47146f9,0x9748e2826cdee284, - 0xe3e27a444d8d98b7,0xfd1b1b2308169b25, - 0x8e6d8c6ab0787f72,0xfe30f0f5e50e20f7, - 0xb208ef855c969f4f,0xbdbd2d335e51a935, - 0xde8b2b66b3bc4723,0xad2c788035e61382, - 0x8b16fb203055ac76,0x4c3bcb5021afcc31, - 0xaddcb9e83c6b1793,0xdf4abe242a1bbf3d, - 0xd953e8624b85dd78,0xd71d6dad34a2af0d, - 0x87d4713d6f33aa6b,0x8672648c40e5ad68, - 0xa9c98d8ccb009506,0x680efdaf511f18c2, - 0xd43bf0effdc0ba48,0x212bd1b2566def2, - 0x84a57695fe98746d,0x14bb630f7604b57, - 0xa5ced43b7e3e9188,0x419ea3bd35385e2d, - 0xcf42894a5dce35ea,0x52064cac828675b9, - 0x818995ce7aa0e1b2,0x7343efebd1940993, - 0xa1ebfb4219491a1f,0x1014ebe6c5f90bf8, - 0xca66fa129f9b60a6,0xd41a26e077774ef6, - 0xfd00b897478238d0,0x8920b098955522b4, - 0x9e20735e8cb16382,0x55b46e5f5d5535b0, - 0xc5a890362fddbc62,0xeb2189f734aa831d, - 0xf712b443bbd52b7b,0xa5e9ec7501d523e4, - 0x9a6bb0aa55653b2d,0x47b233c92125366e, - 0xc1069cd4eabe89f8,0x999ec0bb696e840a, - 0xf148440a256e2c76,0xc00670ea43ca250d, - 0x96cd2a865764dbca,0x380406926a5e5728, - 0xbc807527ed3e12bc,0xc605083704f5ecf2, - 0xeba09271e88d976b,0xf7864a44c633682e, - 0x93445b8731587ea3,0x7ab3ee6afbe0211d, - 0xb8157268fdae9e4c,0x5960ea05bad82964, - 0xe61acf033d1a45df,0x6fb92487298e33bd, - 0x8fd0c16206306bab,0xa5d3b6d479f8e056, - 0xb3c4f1ba87bc8696,0x8f48a4899877186c, - 0xe0b62e2929aba83c,0x331acdabfe94de87, - 0x8c71dcd9ba0b4925,0x9ff0c08b7f1d0b14, - 0xaf8e5410288e1b6f,0x7ecf0ae5ee44dd9, - 0xdb71e91432b1a24a,0xc9e82cd9f69d6150, - 0x892731ac9faf056e,0xbe311c083a225cd2, - 0xab70fe17c79ac6ca,0x6dbd630a48aaf406, - 0xd64d3d9db981787d,0x92cbbccdad5b108, - 0x85f0468293f0eb4e,0x25bbf56008c58ea5, - 0xa76c582338ed2621,0xaf2af2b80af6f24e, - 0xd1476e2c07286faa,0x1af5af660db4aee1, - 0x82cca4db847945ca,0x50d98d9fc890ed4d, - 0xa37fce126597973c,0xe50ff107bab528a0, - 0xcc5fc196fefd7d0c,0x1e53ed49a96272c8, - 0xff77b1fcbebcdc4f,0x25e8e89c13bb0f7a, - 0x9faacf3df73609b1,0x77b191618c54e9ac, - 0xc795830d75038c1d,0xd59df5b9ef6a2417, - 0xf97ae3d0d2446f25,0x4b0573286b44ad1d, - 0x9becce62836ac577,0x4ee367f9430aec32, - 0xc2e801fb244576d5,0x229c41f793cda73f, - 0xf3a20279ed56d48a,0x6b43527578c1110f, - 0x9845418c345644d6,0x830a13896b78aaa9, - 0xbe5691ef416bd60c,0x23cc986bc656d553, - 0xedec366b11c6cb8f,0x2cbfbe86b7ec8aa8, - 0x94b3a202eb1c3f39,0x7bf7d71432f3d6a9, - 0xb9e08a83a5e34f07,0xdaf5ccd93fb0cc53, - 0xe858ad248f5c22c9,0xd1b3400f8f9cff68, - 0x91376c36d99995be,0x23100809b9c21fa1, - 0xb58547448ffffb2d,0xabd40a0c2832a78a, - 0xe2e69915b3fff9f9,0x16c90c8f323f516c, - 0x8dd01fad907ffc3b,0xae3da7d97f6792e3, - 0xb1442798f49ffb4a,0x99cd11cfdf41779c, - 0xdd95317f31c7fa1d,0x40405643d711d583, - 0x8a7d3eef7f1cfc52,0x482835ea666b2572, - 0xad1c8eab5ee43b66,0xda3243650005eecf, - 0xd863b256369d4a40,0x90bed43e40076a82, - 0x873e4f75e2224e68,0x5a7744a6e804a291, - 0xa90de3535aaae202,0x711515d0a205cb36, - 0xd3515c2831559a83,0xd5a5b44ca873e03, - 0x8412d9991ed58091,0xe858790afe9486c2, - 0xa5178fff668ae0b6,0x626e974dbe39a872, - 0xce5d73ff402d98e3,0xfb0a3d212dc8128f, - 0x80fa687f881c7f8e,0x7ce66634bc9d0b99, - 0xa139029f6a239f72,0x1c1fffc1ebc44e80, - 0xc987434744ac874e,0xa327ffb266b56220, - 0xfbe9141915d7a922,0x4bf1ff9f0062baa8, - 0x9d71ac8fada6c9b5,0x6f773fc3603db4a9, - 0xc4ce17b399107c22,0xcb550fb4384d21d3, - 0xf6019da07f549b2b,0x7e2a53a146606a48, - 0x99c102844f94e0fb,0x2eda7444cbfc426d, - 0xc0314325637a1939,0xfa911155fefb5308, - 0xf03d93eebc589f88,0x793555ab7eba27ca, - 0x96267c7535b763b5,0x4bc1558b2f3458de, - 0xbbb01b9283253ca2,0x9eb1aaedfb016f16, - 0xea9c227723ee8bcb,0x465e15a979c1cadc, - 0x92a1958a7675175f,0xbfacd89ec191ec9, - 0xb749faed14125d36,0xcef980ec671f667b, - 0xe51c79a85916f484,0x82b7e12780e7401a, - 0x8f31cc0937ae58d2,0xd1b2ecb8b0908810, - 0xb2fe3f0b8599ef07,0x861fa7e6dcb4aa15, - 0xdfbdcece67006ac9,0x67a791e093e1d49a, - 0x8bd6a141006042bd,0xe0c8bb2c5c6d24e0, - 0xaecc49914078536d,0x58fae9f773886e18, - 0xda7f5bf590966848,0xaf39a475506a899e, - 0x888f99797a5e012d,0x6d8406c952429603, - 0xaab37fd7d8f58178,0xc8e5087ba6d33b83, - 0xd5605fcdcf32e1d6,0xfb1e4a9a90880a64, - 0x855c3be0a17fcd26,0x5cf2eea09a55067f, - 0xa6b34ad8c9dfc06f,0xf42faa48c0ea481e, - 0xd0601d8efc57b08b,0xf13b94daf124da26, - 0x823c12795db6ce57,0x76c53d08d6b70858, - 0xa2cb1717b52481ed,0x54768c4b0c64ca6e, - 0xcb7ddcdda26da268,0xa9942f5dcf7dfd09, - 0xfe5d54150b090b02,0xd3f93b35435d7c4c, - 0x9efa548d26e5a6e1,0xc47bc5014a1a6daf, - 0xc6b8e9b0709f109a,0x359ab6419ca1091b, - 0xf867241c8cc6d4c0,0xc30163d203c94b62, - 0x9b407691d7fc44f8,0x79e0de63425dcf1d, - 0xc21094364dfb5636,0x985915fc12f542e4, - 0xf294b943e17a2bc4,0x3e6f5b7b17b2939d, - 0x979cf3ca6cec5b5a,0xa705992ceecf9c42, - 0xbd8430bd08277231,0x50c6ff782a838353, - 0xece53cec4a314ebd,0xa4f8bf5635246428, - 0x940f4613ae5ed136,0x871b7795e136be99, - 0xb913179899f68584,0x28e2557b59846e3f, - 0xe757dd7ec07426e5,0x331aeada2fe589cf, - 0x9096ea6f3848984f,0x3ff0d2c85def7621, - 0xb4bca50b065abe63,0xfed077a756b53a9, - 0xe1ebce4dc7f16dfb,0xd3e8495912c62894, - 0x8d3360f09cf6e4bd,0x64712dd7abbbd95c, - 0xb080392cc4349dec,0xbd8d794d96aacfb3, - 0xdca04777f541c567,0xecf0d7a0fc5583a0, - 0x89e42caaf9491b60,0xf41686c49db57244, - 0xac5d37d5b79b6239,0x311c2875c522ced5, - 0xd77485cb25823ac7,0x7d633293366b828b, - 0x86a8d39ef77164bc,0xae5dff9c02033197, - 0xa8530886b54dbdeb,0xd9f57f830283fdfc, - 0xd267caa862a12d66,0xd072df63c324fd7b, - 0x8380dea93da4bc60,0x4247cb9e59f71e6d, - 0xa46116538d0deb78,0x52d9be85f074e608, - 0xcd795be870516656,0x67902e276c921f8b, - 0x806bd9714632dff6,0xba1cd8a3db53b6, - 0xa086cfcd97bf97f3,0x80e8a40eccd228a4, - 0xc8a883c0fdaf7df0,0x6122cd128006b2cd, - 0xfad2a4b13d1b5d6c,0x796b805720085f81, - 0x9cc3a6eec6311a63,0xcbe3303674053bb0, - 0xc3f490aa77bd60fc,0xbedbfc4411068a9c, - 0xf4f1b4d515acb93b,0xee92fb5515482d44, - 0x991711052d8bf3c5,0x751bdd152d4d1c4a, - 0xbf5cd54678eef0b6,0xd262d45a78a0635d, - 0xef340a98172aace4,0x86fb897116c87c34, - 0x9580869f0e7aac0e,0xd45d35e6ae3d4da0, - 0xbae0a846d2195712,0x8974836059cca109, - 0xe998d258869facd7,0x2bd1a438703fc94b, - 0x91ff83775423cc06,0x7b6306a34627ddcf, - 0xb67f6455292cbf08,0x1a3bc84c17b1d542, - 0xe41f3d6a7377eeca,0x20caba5f1d9e4a93, - 0x8e938662882af53e,0x547eb47b7282ee9c, - 0xb23867fb2a35b28d,0xe99e619a4f23aa43, - 0xdec681f9f4c31f31,0x6405fa00e2ec94d4, - 0x8b3c113c38f9f37e,0xde83bc408dd3dd04, - 0xae0b158b4738705e,0x9624ab50b148d445, - 0xd98ddaee19068c76,0x3badd624dd9b0957, - 0x87f8a8d4cfa417c9,0xe54ca5d70a80e5d6, - 0xa9f6d30a038d1dbc,0x5e9fcf4ccd211f4c, - 0xd47487cc8470652b,0x7647c3200069671f, - 0x84c8d4dfd2c63f3b,0x29ecd9f40041e073, - 0xa5fb0a17c777cf09,0xf468107100525890, - 0xcf79cc9db955c2cc,0x7182148d4066eeb4, - 0x81ac1fe293d599bf,0xc6f14cd848405530, - 0xa21727db38cb002f,0xb8ada00e5a506a7c, - 0xca9cf1d206fdc03b,0xa6d90811f0e4851c, - 0xfd442e4688bd304a,0x908f4a166d1da663, - 0x9e4a9cec15763e2e,0x9a598e4e043287fe, - 0xc5dd44271ad3cdba,0x40eff1e1853f29fd, - 0xf7549530e188c128,0xd12bee59e68ef47c, - 0x9a94dd3e8cf578b9,0x82bb74f8301958ce, - 0xc13a148e3032d6e7,0xe36a52363c1faf01, - 0xf18899b1bc3f8ca1,0xdc44e6c3cb279ac1, - 0x96f5600f15a7b7e5,0x29ab103a5ef8c0b9, - 0xbcb2b812db11a5de,0x7415d448f6b6f0e7, - 0xebdf661791d60f56,0x111b495b3464ad21, - 0x936b9fcebb25c995,0xcab10dd900beec34, - 0xb84687c269ef3bfb,0x3d5d514f40eea742, - 0xe65829b3046b0afa,0xcb4a5a3112a5112, - 0x8ff71a0fe2c2e6dc,0x47f0e785eaba72ab, - 0xb3f4e093db73a093,0x59ed216765690f56, - 0xe0f218b8d25088b8,0x306869c13ec3532c, - 0x8c974f7383725573,0x1e414218c73a13fb, - 0xafbd2350644eeacf,0xe5d1929ef90898fa, - 0xdbac6c247d62a583,0xdf45f746b74abf39, - 0x894bc396ce5da772,0x6b8bba8c328eb783, - 0xab9eb47c81f5114f,0x66ea92f3f326564, - 0xd686619ba27255a2,0xc80a537b0efefebd, - 0x8613fd0145877585,0xbd06742ce95f5f36, - 0xa798fc4196e952e7,0x2c48113823b73704, - 0xd17f3b51fca3a7a0,0xf75a15862ca504c5, - 0x82ef85133de648c4,0x9a984d73dbe722fb, - 0xa3ab66580d5fdaf5,0xc13e60d0d2e0ebba, - 0xcc963fee10b7d1b3,0x318df905079926a8, - 0xffbbcfe994e5c61f,0xfdf17746497f7052, - 0x9fd561f1fd0f9bd3,0xfeb6ea8bedefa633, - 0xc7caba6e7c5382c8,0xfe64a52ee96b8fc0, - 0xf9bd690a1b68637b,0x3dfdce7aa3c673b0, - 0x9c1661a651213e2d,0x6bea10ca65c084e, - 0xc31bfa0fe5698db8,0x486e494fcff30a62, - 0xf3e2f893dec3f126,0x5a89dba3c3efccfa, - 0x986ddb5c6b3a76b7,0xf89629465a75e01c, - 0xbe89523386091465,0xf6bbb397f1135823, - 0xee2ba6c0678b597f,0x746aa07ded582e2c, - 0x94db483840b717ef,0xa8c2a44eb4571cdc, - 0xba121a4650e4ddeb,0x92f34d62616ce413, - 0xe896a0d7e51e1566,0x77b020baf9c81d17, - 0x915e2486ef32cd60,0xace1474dc1d122e, - 0xb5b5ada8aaff80b8,0xd819992132456ba, - 0xe3231912d5bf60e6,0x10e1fff697ed6c69, - 0x8df5efabc5979c8f,0xca8d3ffa1ef463c1, - 0xb1736b96b6fd83b3,0xbd308ff8a6b17cb2, - 0xddd0467c64bce4a0,0xac7cb3f6d05ddbde, - 0x8aa22c0dbef60ee4,0x6bcdf07a423aa96b, - 0xad4ab7112eb3929d,0x86c16c98d2c953c6, - 0xd89d64d57a607744,0xe871c7bf077ba8b7, - 0x87625f056c7c4a8b,0x11471cd764ad4972, - 0xa93af6c6c79b5d2d,0xd598e40d3dd89bcf, - 0xd389b47879823479,0x4aff1d108d4ec2c3, - 0x843610cb4bf160cb,0xcedf722a585139ba, - 0xa54394fe1eedb8fe,0xc2974eb4ee658828, - 0xce947a3da6a9273e,0x733d226229feea32, - 0x811ccc668829b887,0x806357d5a3f525f, - 0xa163ff802a3426a8,0xca07c2dcb0cf26f7, - 0xc9bcff6034c13052,0xfc89b393dd02f0b5, - 0xfc2c3f3841f17c67,0xbbac2078d443ace2, - 0x9d9ba7832936edc0,0xd54b944b84aa4c0d, - 0xc5029163f384a931,0xa9e795e65d4df11, - 0xf64335bcf065d37d,0x4d4617b5ff4a16d5, - 0x99ea0196163fa42e,0x504bced1bf8e4e45, - 0xc06481fb9bcf8d39,0xe45ec2862f71e1d6, - 0xf07da27a82c37088,0x5d767327bb4e5a4c, - 0x964e858c91ba2655,0x3a6a07f8d510f86f, - 0xbbe226efb628afea,0x890489f70a55368b, - 0xeadab0aba3b2dbe5,0x2b45ac74ccea842e, - 0x92c8ae6b464fc96f,0x3b0b8bc90012929d, - 0xb77ada0617e3bbcb,0x9ce6ebb40173744, - 0xe55990879ddcaabd,0xcc420a6a101d0515, - 0x8f57fa54c2a9eab6,0x9fa946824a12232d, - 0xb32df8e9f3546564,0x47939822dc96abf9, - 0xdff9772470297ebd,0x59787e2b93bc56f7, - 0x8bfbea76c619ef36,0x57eb4edb3c55b65a, - 0xaefae51477a06b03,0xede622920b6b23f1, - 0xdab99e59958885c4,0xe95fab368e45eced, - 0x88b402f7fd75539b,0x11dbcb0218ebb414, - 0xaae103b5fcd2a881,0xd652bdc29f26a119, - 0xd59944a37c0752a2,0x4be76d3346f0495f, - 0x857fcae62d8493a5,0x6f70a4400c562ddb, - 0xa6dfbd9fb8e5b88e,0xcb4ccd500f6bb952, - 0xd097ad07a71f26b2,0x7e2000a41346a7a7, - 0x825ecc24c873782f,0x8ed400668c0c28c8, - 0xa2f67f2dfa90563b,0x728900802f0f32fa, - 0xcbb41ef979346bca,0x4f2b40a03ad2ffb9, - 0xfea126b7d78186bc,0xe2f610c84987bfa8, - 0x9f24b832e6b0f436,0xdd9ca7d2df4d7c9, - 0xc6ede63fa05d3143,0x91503d1c79720dbb, - 0xf8a95fcf88747d94,0x75a44c6397ce912a, - 0x9b69dbe1b548ce7c,0xc986afbe3ee11aba, - 0xc24452da229b021b,0xfbe85badce996168, - 0xf2d56790ab41c2a2,0xfae27299423fb9c3, - 0x97c560ba6b0919a5,0xdccd879fc967d41a, - 0xbdb6b8e905cb600f,0x5400e987bbc1c920, - 0xed246723473e3813,0x290123e9aab23b68, - 0x9436c0760c86e30b,0xf9a0b6720aaf6521, - 0xb94470938fa89bce,0xf808e40e8d5b3e69, - 0xe7958cb87392c2c2,0xb60b1d1230b20e04, - 0x90bd77f3483bb9b9,0xb1c6f22b5e6f48c2, - 0xb4ecd5f01a4aa828,0x1e38aeb6360b1af3, - 0xe2280b6c20dd5232,0x25c6da63c38de1b0, - 0x8d590723948a535f,0x579c487e5a38ad0e, - 0xb0af48ec79ace837,0x2d835a9df0c6d851, - 0xdcdb1b2798182244,0xf8e431456cf88e65, - 0x8a08f0f8bf0f156b,0x1b8e9ecb641b58ff, - 0xac8b2d36eed2dac5,0xe272467e3d222f3f, - 0xd7adf884aa879177,0x5b0ed81dcc6abb0f, - 0x86ccbb52ea94baea,0x98e947129fc2b4e9, - 0xa87fea27a539e9a5,0x3f2398d747b36224, - 0xd29fe4b18e88640e,0x8eec7f0d19a03aad, - 0x83a3eeeef9153e89,0x1953cf68300424ac, - 0xa48ceaaab75a8e2b,0x5fa8c3423c052dd7, - 0xcdb02555653131b6,0x3792f412cb06794d, - 0x808e17555f3ebf11,0xe2bbd88bbee40bd0, - 0xa0b19d2ab70e6ed6,0x5b6aceaeae9d0ec4, - 0xc8de047564d20a8b,0xf245825a5a445275, - 0xfb158592be068d2e,0xeed6e2f0f0d56712, - 0x9ced737bb6c4183d,0x55464dd69685606b, - 0xc428d05aa4751e4c,0xaa97e14c3c26b886, - 0xf53304714d9265df,0xd53dd99f4b3066a8, - 0x993fe2c6d07b7fab,0xe546a8038efe4029, - 0xbf8fdb78849a5f96,0xde98520472bdd033, - 0xef73d256a5c0f77c,0x963e66858f6d4440, - 0x95a8637627989aad,0xdde7001379a44aa8, - 0xbb127c53b17ec159,0x5560c018580d5d52, - 0xe9d71b689dde71af,0xaab8f01e6e10b4a6, - 0x9226712162ab070d,0xcab3961304ca70e8, - 0xb6b00d69bb55c8d1,0x3d607b97c5fd0d22, - 0xe45c10c42a2b3b05,0x8cb89a7db77c506a, - 0x8eb98a7a9a5b04e3,0x77f3608e92adb242, - 0xb267ed1940f1c61c,0x55f038b237591ed3, - 0xdf01e85f912e37a3,0x6b6c46dec52f6688, - 0x8b61313bbabce2c6,0x2323ac4b3b3da015, - 0xae397d8aa96c1b77,0xabec975e0a0d081a, - 0xd9c7dced53c72255,0x96e7bd358c904a21, - 0x881cea14545c7575,0x7e50d64177da2e54, - 0xaa242499697392d2,0xdde50bd1d5d0b9e9, - 0xd4ad2dbfc3d07787,0x955e4ec64b44e864, - 0x84ec3c97da624ab4,0xbd5af13bef0b113e, - 0xa6274bbdd0fadd61,0xecb1ad8aeacdd58e, - 0xcfb11ead453994ba,0x67de18eda5814af2, - 0x81ceb32c4b43fcf4,0x80eacf948770ced7, - 0xa2425ff75e14fc31,0xa1258379a94d028d, - 0xcad2f7f5359a3b3e,0x96ee45813a04330, - 0xfd87b5f28300ca0d,0x8bca9d6e188853fc, - 0x9e74d1b791e07e48,0x775ea264cf55347e, - 0xc612062576589dda,0x95364afe032a819e, - 0xf79687aed3eec551,0x3a83ddbd83f52205, - 0x9abe14cd44753b52,0xc4926a9672793543, - 0xc16d9a0095928a27,0x75b7053c0f178294, - 0xf1c90080baf72cb1,0x5324c68b12dd6339, - 0x971da05074da7bee,0xd3f6fc16ebca5e04, - 0xbce5086492111aea,0x88f4bb1ca6bcf585, - 0xec1e4a7db69561a5,0x2b31e9e3d06c32e6, - 0x9392ee8e921d5d07,0x3aff322e62439fd0, - 0xb877aa3236a4b449,0x9befeb9fad487c3, - 0xe69594bec44de15b,0x4c2ebe687989a9b4, - 0x901d7cf73ab0acd9,0xf9d37014bf60a11, - 0xb424dc35095cd80f,0x538484c19ef38c95, - 0xe12e13424bb40e13,0x2865a5f206b06fba, - 0x8cbccc096f5088cb,0xf93f87b7442e45d4, - 0xafebff0bcb24aafe,0xf78f69a51539d749, - 0xdbe6fecebdedd5be,0xb573440e5a884d1c, - 0x89705f4136b4a597,0x31680a88f8953031, - 0xabcc77118461cefc,0xfdc20d2b36ba7c3e, - 0xd6bf94d5e57a42bc,0x3d32907604691b4d, - 0x8637bd05af6c69b5,0xa63f9a49c2c1b110, - 0xa7c5ac471b478423,0xfcf80dc33721d54, - 0xd1b71758e219652b,0xd3c36113404ea4a9, - 0x83126e978d4fdf3b,0x645a1cac083126ea, - 0xa3d70a3d70a3d70a,0x3d70a3d70a3d70a4, - 0xcccccccccccccccc,0xcccccccccccccccd, - 0x8000000000000000,0x0, - 0xa000000000000000,0x0, - 0xc800000000000000,0x0, - 0xfa00000000000000,0x0, - 0x9c40000000000000,0x0, - 0xc350000000000000,0x0, - 0xf424000000000000,0x0, - 0x9896800000000000,0x0, - 0xbebc200000000000,0x0, - 0xee6b280000000000,0x0, - 0x9502f90000000000,0x0, - 0xba43b74000000000,0x0, - 0xe8d4a51000000000,0x0, - 0x9184e72a00000000,0x0, - 0xb5e620f480000000,0x0, - 0xe35fa931a0000000,0x0, - 0x8e1bc9bf04000000,0x0, - 0xb1a2bc2ec5000000,0x0, - 0xde0b6b3a76400000,0x0, - 0x8ac7230489e80000,0x0, - 0xad78ebc5ac620000,0x0, - 0xd8d726b7177a8000,0x0, - 0x878678326eac9000,0x0, - 0xa968163f0a57b400,0x0, - 0xd3c21bcecceda100,0x0, - 0x84595161401484a0,0x0, - 0xa56fa5b99019a5c8,0x0, - 0xcecb8f27f4200f3a,0x0, - 0x813f3978f8940984,0x4000000000000000, - 0xa18f07d736b90be5,0x5000000000000000, - 0xc9f2c9cd04674ede,0xa400000000000000, - 0xfc6f7c4045812296,0x4d00000000000000, - 0x9dc5ada82b70b59d,0xf020000000000000, - 0xc5371912364ce305,0x6c28000000000000, - 0xf684df56c3e01bc6,0xc732000000000000, - 0x9a130b963a6c115c,0x3c7f400000000000, - 0xc097ce7bc90715b3,0x4b9f100000000000, - 0xf0bdc21abb48db20,0x1e86d40000000000, - 0x96769950b50d88f4,0x1314448000000000, - 0xbc143fa4e250eb31,0x17d955a000000000, - 0xeb194f8e1ae525fd,0x5dcfab0800000000, - 0x92efd1b8d0cf37be,0x5aa1cae500000000, - 0xb7abc627050305ad,0xf14a3d9e40000000, - 0xe596b7b0c643c719,0x6d9ccd05d0000000, - 0x8f7e32ce7bea5c6f,0xe4820023a2000000, - 0xb35dbf821ae4f38b,0xdda2802c8a800000, - 0xe0352f62a19e306e,0xd50b2037ad200000, - 0x8c213d9da502de45,0x4526f422cc340000, - 0xaf298d050e4395d6,0x9670b12b7f410000, - 0xdaf3f04651d47b4c,0x3c0cdd765f114000, - 0x88d8762bf324cd0f,0xa5880a69fb6ac800, - 0xab0e93b6efee0053,0x8eea0d047a457a00, - 0xd5d238a4abe98068,0x72a4904598d6d880, - 0x85a36366eb71f041,0x47a6da2b7f864750, - 0xa70c3c40a64e6c51,0x999090b65f67d924, - 0xd0cf4b50cfe20765,0xfff4b4e3f741cf6d, - 0x82818f1281ed449f,0xbff8f10e7a8921a4, - 0xa321f2d7226895c7,0xaff72d52192b6a0d, - 0xcbea6f8ceb02bb39,0x9bf4f8a69f764490, - 0xfee50b7025c36a08,0x2f236d04753d5b4, - 0x9f4f2726179a2245,0x1d762422c946590, - 0xc722f0ef9d80aad6,0x424d3ad2b7b97ef5, - 0xf8ebad2b84e0d58b,0xd2e0898765a7deb2, - 0x9b934c3b330c8577,0x63cc55f49f88eb2f, - 0xc2781f49ffcfa6d5,0x3cbf6b71c76b25fb, - 0xf316271c7fc3908a,0x8bef464e3945ef7a, - 0x97edd871cfda3a56,0x97758bf0e3cbb5ac, - 0xbde94e8e43d0c8ec,0x3d52eeed1cbea317, - 0xed63a231d4c4fb27,0x4ca7aaa863ee4bdd, - 0x945e455f24fb1cf8,0x8fe8caa93e74ef6a, - 0xb975d6b6ee39e436,0xb3e2fd538e122b44, - 0xe7d34c64a9c85d44,0x60dbbca87196b616, - 0x90e40fbeea1d3a4a,0xbc8955e946fe31cd, - 0xb51d13aea4a488dd,0x6babab6398bdbe41, - 0xe264589a4dcdab14,0xc696963c7eed2dd1, - 0x8d7eb76070a08aec,0xfc1e1de5cf543ca2, - 0xb0de65388cc8ada8,0x3b25a55f43294bcb, - 0xdd15fe86affad912,0x49ef0eb713f39ebe, - 0x8a2dbf142dfcc7ab,0x6e3569326c784337, - 0xacb92ed9397bf996,0x49c2c37f07965404, - 0xd7e77a8f87daf7fb,0xdc33745ec97be906, - 0x86f0ac99b4e8dafd,0x69a028bb3ded71a3, - 0xa8acd7c0222311bc,0xc40832ea0d68ce0c, - 0xd2d80db02aabd62b,0xf50a3fa490c30190, - 0x83c7088e1aab65db,0x792667c6da79e0fa, - 0xa4b8cab1a1563f52,0x577001b891185938, - 0xcde6fd5e09abcf26,0xed4c0226b55e6f86, - 0x80b05e5ac60b6178,0x544f8158315b05b4, - 0xa0dc75f1778e39d6,0x696361ae3db1c721, - 0xc913936dd571c84c,0x3bc3a19cd1e38e9, - 0xfb5878494ace3a5f,0x4ab48a04065c723, - 0x9d174b2dcec0e47b,0x62eb0d64283f9c76, - 0xc45d1df942711d9a,0x3ba5d0bd324f8394, - 0xf5746577930d6500,0xca8f44ec7ee36479, - 0x9968bf6abbe85f20,0x7e998b13cf4e1ecb, - 0xbfc2ef456ae276e8,0x9e3fedd8c321a67e, - 0xefb3ab16c59b14a2,0xc5cfe94ef3ea101e, - 0x95d04aee3b80ece5,0xbba1f1d158724a12, - 0xbb445da9ca61281f,0x2a8a6e45ae8edc97, - 0xea1575143cf97226,0xf52d09d71a3293bd, - 0x924d692ca61be758,0x593c2626705f9c56, - 0xb6e0c377cfa2e12e,0x6f8b2fb00c77836c, - 0xe498f455c38b997a,0xb6dfb9c0f956447, - 0x8edf98b59a373fec,0x4724bd4189bd5eac, - 0xb2977ee300c50fe7,0x58edec91ec2cb657, - 0xdf3d5e9bc0f653e1,0x2f2967b66737e3ed, - 0x8b865b215899f46c,0xbd79e0d20082ee74, - 0xae67f1e9aec07187,0xecd8590680a3aa11, - 0xda01ee641a708de9,0xe80e6f4820cc9495, - 0x884134fe908658b2,0x3109058d147fdcdd, - 0xaa51823e34a7eede,0xbd4b46f0599fd415, - 0xd4e5e2cdc1d1ea96,0x6c9e18ac7007c91a, - 0x850fadc09923329e,0x3e2cf6bc604ddb0, - 0xa6539930bf6bff45,0x84db8346b786151c, - 0xcfe87f7cef46ff16,0xe612641865679a63, - 0x81f14fae158c5f6e,0x4fcb7e8f3f60c07e, - 0xa26da3999aef7749,0xe3be5e330f38f09d, - 0xcb090c8001ab551c,0x5cadf5bfd3072cc5, - 0xfdcb4fa002162a63,0x73d9732fc7c8f7f6, - 0x9e9f11c4014dda7e,0x2867e7fddcdd9afa, - 0xc646d63501a1511d,0xb281e1fd541501b8, - 0xf7d88bc24209a565,0x1f225a7ca91a4226, - 0x9ae757596946075f,0x3375788de9b06958, - 0xc1a12d2fc3978937,0x52d6b1641c83ae, - 0xf209787bb47d6b84,0xc0678c5dbd23a49a, - 0x9745eb4d50ce6332,0xf840b7ba963646e0, - 0xbd176620a501fbff,0xb650e5a93bc3d898, - 0xec5d3fa8ce427aff,0xa3e51f138ab4cebe, - 0x93ba47c980e98cdf,0xc66f336c36b10137, - 0xb8a8d9bbe123f017,0xb80b0047445d4184, - 0xe6d3102ad96cec1d,0xa60dc059157491e5, - 0x9043ea1ac7e41392,0x87c89837ad68db2f, - 0xb454e4a179dd1877,0x29babe4598c311fb, - 0xe16a1dc9d8545e94,0xf4296dd6fef3d67a, - 0x8ce2529e2734bb1d,0x1899e4a65f58660c, - 0xb01ae745b101e9e4,0x5ec05dcff72e7f8f, - 0xdc21a1171d42645d,0x76707543f4fa1f73, - 0x899504ae72497eba,0x6a06494a791c53a8, - 0xabfa45da0edbde69,0x487db9d17636892, - 0xd6f8d7509292d603,0x45a9d2845d3c42b6, - 0x865b86925b9bc5c2,0xb8a2392ba45a9b2, - 0xa7f26836f282b732,0x8e6cac7768d7141e, - 0xd1ef0244af2364ff,0x3207d795430cd926, - 0x8335616aed761f1f,0x7f44e6bd49e807b8, - 0xa402b9c5a8d3a6e7,0x5f16206c9c6209a6, - 0xcd036837130890a1,0x36dba887c37a8c0f, - 0x802221226be55a64,0xc2494954da2c9789, - 0xa02aa96b06deb0fd,0xf2db9baa10b7bd6c, - 0xc83553c5c8965d3d,0x6f92829494e5acc7, - 0xfa42a8b73abbf48c,0xcb772339ba1f17f9, - 0x9c69a97284b578d7,0xff2a760414536efb, - 0xc38413cf25e2d70d,0xfef5138519684aba, - 0xf46518c2ef5b8cd1,0x7eb258665fc25d69, - 0x98bf2f79d5993802,0xef2f773ffbd97a61, - 0xbeeefb584aff8603,0xaafb550ffacfd8fa, - 0xeeaaba2e5dbf6784,0x95ba2a53f983cf38, - 0x952ab45cfa97a0b2,0xdd945a747bf26183, - 0xba756174393d88df,0x94f971119aeef9e4, - 0xe912b9d1478ceb17,0x7a37cd5601aab85d, - 0x91abb422ccb812ee,0xac62e055c10ab33a, - 0xb616a12b7fe617aa,0x577b986b314d6009, - 0xe39c49765fdf9d94,0xed5a7e85fda0b80b, - 0x8e41ade9fbebc27d,0x14588f13be847307, - 0xb1d219647ae6b31c,0x596eb2d8ae258fc8, - 0xde469fbd99a05fe3,0x6fca5f8ed9aef3bb, - 0x8aec23d680043bee,0x25de7bb9480d5854, - 0xada72ccc20054ae9,0xaf561aa79a10ae6a, - 0xd910f7ff28069da4,0x1b2ba1518094da04, - 0x87aa9aff79042286,0x90fb44d2f05d0842, - 0xa99541bf57452b28,0x353a1607ac744a53, - 0xd3fa922f2d1675f2,0x42889b8997915ce8, - 0x847c9b5d7c2e09b7,0x69956135febada11, - 0xa59bc234db398c25,0x43fab9837e699095, - 0xcf02b2c21207ef2e,0x94f967e45e03f4bb, - 0x8161afb94b44f57d,0x1d1be0eebac278f5, - 0xa1ba1ba79e1632dc,0x6462d92a69731732, - 0xca28a291859bbf93,0x7d7b8f7503cfdcfe, - 0xfcb2cb35e702af78,0x5cda735244c3d43e, - 0x9defbf01b061adab,0x3a0888136afa64a7, - 0xc56baec21c7a1916,0x88aaa1845b8fdd0, - 0xf6c69a72a3989f5b,0x8aad549e57273d45, - 0x9a3c2087a63f6399,0x36ac54e2f678864b, - 0xc0cb28a98fcf3c7f,0x84576a1bb416a7dd, - 0xf0fdf2d3f3c30b9f,0x656d44a2a11c51d5, - 0x969eb7c47859e743,0x9f644ae5a4b1b325, - 0xbc4665b596706114,0x873d5d9f0dde1fee, - 0xeb57ff22fc0c7959,0xa90cb506d155a7ea, - 0x9316ff75dd87cbd8,0x9a7f12442d588f2, - 0xb7dcbf5354e9bece,0xc11ed6d538aeb2f, - 0xe5d3ef282a242e81,0x8f1668c8a86da5fa, - 0x8fa475791a569d10,0xf96e017d694487bc, - 0xb38d92d760ec4455,0x37c981dcc395a9ac, - 0xe070f78d3927556a,0x85bbe253f47b1417, - 0x8c469ab843b89562,0x93956d7478ccec8e, - 0xaf58416654a6babb,0x387ac8d1970027b2, - 0xdb2e51bfe9d0696a,0x6997b05fcc0319e, - 0x88fcf317f22241e2,0x441fece3bdf81f03, - 0xab3c2fddeeaad25a,0xd527e81cad7626c3, - 0xd60b3bd56a5586f1,0x8a71e223d8d3b074, - 0x85c7056562757456,0xf6872d5667844e49, - 0xa738c6bebb12d16c,0xb428f8ac016561db, - 0xd106f86e69d785c7,0xe13336d701beba52, - 0x82a45b450226b39c,0xecc0024661173473, - 0xa34d721642b06084,0x27f002d7f95d0190, - 0xcc20ce9bd35c78a5,0x31ec038df7b441f4, - 0xff290242c83396ce,0x7e67047175a15271, - 0x9f79a169bd203e41,0xf0062c6e984d386, - 0xc75809c42c684dd1,0x52c07b78a3e60868, - 0xf92e0c3537826145,0xa7709a56ccdf8a82, - 0x9bbcc7a142b17ccb,0x88a66076400bb691, - 0xc2abf989935ddbfe,0x6acff893d00ea435, - 0xf356f7ebf83552fe,0x583f6b8c4124d43, - 0x98165af37b2153de,0xc3727a337a8b704a, - 0xbe1bf1b059e9a8d6,0x744f18c0592e4c5c, - 0xeda2ee1c7064130c,0x1162def06f79df73, - 0x9485d4d1c63e8be7,0x8addcb5645ac2ba8, - 0xb9a74a0637ce2ee1,0x6d953e2bd7173692, - 0xe8111c87c5c1ba99,0xc8fa8db6ccdd0437, - 0x910ab1d4db9914a0,0x1d9c9892400a22a2, - 0xb54d5e4a127f59c8,0x2503beb6d00cab4b, - 0xe2a0b5dc971f303a,0x2e44ae64840fd61d, - 0x8da471a9de737e24,0x5ceaecfed289e5d2, - 0xb10d8e1456105dad,0x7425a83e872c5f47, - 0xdd50f1996b947518,0xd12f124e28f77719, - 0x8a5296ffe33cc92f,0x82bd6b70d99aaa6f, - 0xace73cbfdc0bfb7b,0x636cc64d1001550b, - 0xd8210befd30efa5a,0x3c47f7e05401aa4e, - 0x8714a775e3e95c78,0x65acfaec34810a71, - 0xa8d9d1535ce3b396,0x7f1839a741a14d0d, - 0xd31045a8341ca07c,0x1ede48111209a050, - 0x83ea2b892091e44d,0x934aed0aab460432, - 0xa4e4b66b68b65d60,0xf81da84d5617853f, - 0xce1de40642e3f4b9,0x36251260ab9d668e, - 0x80d2ae83e9ce78f3,0xc1d72b7c6b426019, - 0xa1075a24e4421730,0xb24cf65b8612f81f, - 0xc94930ae1d529cfc,0xdee033f26797b627, - 0xfb9b7cd9a4a7443c,0x169840ef017da3b1, - 0x9d412e0806e88aa5,0x8e1f289560ee864e, - 0xc491798a08a2ad4e,0xf1a6f2bab92a27e2, - 0xf5b5d7ec8acb58a2,0xae10af696774b1db, - 0x9991a6f3d6bf1765,0xacca6da1e0a8ef29, - 0xbff610b0cc6edd3f,0x17fd090a58d32af3, - 0xeff394dcff8a948e,0xddfc4b4cef07f5b0, - 0x95f83d0a1fb69cd9,0x4abdaf101564f98e, - 0xbb764c4ca7a4440f,0x9d6d1ad41abe37f1, - 0xea53df5fd18d5513,0x84c86189216dc5ed, - 0x92746b9be2f8552c,0x32fd3cf5b4e49bb4, - 0xb7118682dbb66a77,0x3fbc8c33221dc2a1, - 0xe4d5e82392a40515,0xfabaf3feaa5334a, - 0x8f05b1163ba6832d,0x29cb4d87f2a7400e, - 0xb2c71d5bca9023f8,0x743e20e9ef511012, - 0xdf78e4b2bd342cf6,0x914da9246b255416, - 0x8bab8eefb6409c1a,0x1ad089b6c2f7548e, - 0xae9672aba3d0c320,0xa184ac2473b529b1, - 0xda3c0f568cc4f3e8,0xc9e5d72d90a2741e, - 0x8865899617fb1871,0x7e2fa67c7a658892, - 0xaa7eebfb9df9de8d,0xddbb901b98feeab7, - 0xd51ea6fa85785631,0x552a74227f3ea565, - 0x8533285c936b35de,0xd53a88958f87275f, - 0xa67ff273b8460356,0x8a892abaf368f137, - 0xd01fef10a657842c,0x2d2b7569b0432d85, - 0x8213f56a67f6b29b,0x9c3b29620e29fc73, - 0xa298f2c501f45f42,0x8349f3ba91b47b8f, - 0xcb3f2f7642717713,0x241c70a936219a73, - 0xfe0efb53d30dd4d7,0xed238cd383aa0110, - 0x9ec95d1463e8a506,0xf4363804324a40aa, - 0xc67bb4597ce2ce48,0xb143c6053edcd0d5, - 0xf81aa16fdc1b81da,0xdd94b7868e94050a, - 0x9b10a4e5e9913128,0xca7cf2b4191c8326, - 0xc1d4ce1f63f57d72,0xfd1c2f611f63a3f0, - 0xf24a01a73cf2dccf,0xbc633b39673c8cec, - 0x976e41088617ca01,0xd5be0503e085d813, - 0xbd49d14aa79dbc82,0x4b2d8644d8a74e18, - 0xec9c459d51852ba2,0xddf8e7d60ed1219e, - 0x93e1ab8252f33b45,0xcabb90e5c942b503, - 0xb8da1662e7b00a17,0x3d6a751f3b936243, - 0xe7109bfba19c0c9d,0xcc512670a783ad4, - 0x906a617d450187e2,0x27fb2b80668b24c5, - 0xb484f9dc9641e9da,0xb1f9f660802dedf6, - 0xe1a63853bbd26451,0x5e7873f8a0396973, - 0x8d07e33455637eb2,0xdb0b487b6423e1e8, - 0xb049dc016abc5e5f,0x91ce1a9a3d2cda62, - 0xdc5c5301c56b75f7,0x7641a140cc7810fb, - 0x89b9b3e11b6329ba,0xa9e904c87fcb0a9d, - 0xac2820d9623bf429,0x546345fa9fbdcd44, - 0xd732290fbacaf133,0xa97c177947ad4095, - 0x867f59a9d4bed6c0,0x49ed8eabcccc485d, - 0xa81f301449ee8c70,0x5c68f256bfff5a74, - 0xd226fc195c6a2f8c,0x73832eec6fff3111, - 0x83585d8fd9c25db7,0xc831fd53c5ff7eab, - 0xa42e74f3d032f525,0xba3e7ca8b77f5e55, - 0xcd3a1230c43fb26f,0x28ce1bd2e55f35eb, - 0x80444b5e7aa7cf85,0x7980d163cf5b81b3, - 0xa0555e361951c366,0xd7e105bcc332621f, - 0xc86ab5c39fa63440,0x8dd9472bf3fefaa7, - 0xfa856334878fc150,0xb14f98f6f0feb951, - 0x9c935e00d4b9d8d2,0x6ed1bf9a569f33d3, - 0xc3b8358109e84f07,0xa862f80ec4700c8, - 0xf4a642e14c6262c8,0xcd27bb612758c0fa, - 0x98e7e9cccfbd7dbd,0x8038d51cb897789c, - 0xbf21e44003acdd2c,0xe0470a63e6bd56c3, - 0xeeea5d5004981478,0x1858ccfce06cac74, - 0x95527a5202df0ccb,0xf37801e0c43ebc8, - 0xbaa718e68396cffd,0xd30560258f54e6ba, - 0xe950df20247c83fd,0x47c6b82ef32a2069, - 0x91d28b7416cdd27e,0x4cdc331d57fa5441, - 0xb6472e511c81471d,0xe0133fe4adf8e952, - 0xe3d8f9e563a198e5,0x58180fddd97723a6, - 0x8e679c2f5e44ff8f,0x570f09eaa7ea7648,}; -}; - -template -constexpr uint64_t powers_template::power_of_five_128[number_of_entries]; - -using powers = powers_template<>; - -} // namespace fast_float - -#endif - -#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H -#define FASTFLOAT_DECIMAL_TO_BINARY_H - -#include -#include -#include -#include -#include -#include - -namespace fast_float { - -// This will compute or rather approximate w * 5**q and return a pair of 64-bit words approximating -// the result, with the "high" part corresponding to the most significant bits and the -// low part corresponding to the least significant bits. -// -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -value128 compute_product_approximation(int64_t q, uint64_t w) { - const int index = 2 * int(q - powers::smallest_power_of_five); - // For small values of q, e.g., q in [0,27], the answer is always exact because - // The line value128 firstproduct = full_multiplication(w, power_of_five_128[index]); - // gives the exact answer. - value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]); - static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]"); - constexpr uint64_t precision_mask = (bit_precision < 64) ? - (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) - : uint64_t(0xFFFFFFFFFFFFFFFF); - if((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower) - // regarding the second product, we only need secondproduct.high, but our expectation is that the compiler will optimize this extra work away if needed. - value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]); - firstproduct.low += secondproduct.high; - if(secondproduct.high > firstproduct.low) { - firstproduct.high++; - } - } - return firstproduct; -} - -namespace detail { -/** - * For q in (0,350), we have that - * f = (((152170 + 65536) * q ) >> 16); - * is equal to - * floor(p) + q - * where - * p = log(5**q)/log(2) = q * log(5)/log(2) - * - * For negative values of q in (-400,0), we have that - * f = (((152170 + 65536) * q ) >> 16); - * is equal to - * -ceil(p) + q - * where - * p = log(5**-q)/log(2) = -q * log(5)/log(2) - */ - constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { - return (((152170 + 65536) * q) >> 16) + 63; - } -} // namespace detail - -// create an adjusted mantissa, biased by the invalid power2 -// for significant digits already multiplied by 10 ** q. -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 -adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { - int hilz = int(w >> 63) ^ 1; - adjusted_mantissa answer; - answer.mantissa = w << hilz; - int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); - answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + invalid_am_bias); - return answer; -} - -// w * 10 ** q, without rounding the representation up. -// the power2 in the exponent will be adjusted by invalid_am_bias. -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept { - int lz = leading_zeroes(w); - w <<= lz; - value128 product = compute_product_approximation(q, w); - return compute_error_scaled(q, product.high, lz); -} - -// w * 10 ** q -// The returned value should be a valid ieee64 number that simply need to be packed. -// However, in some very rare cases, the computation will fail. In such cases, we -// return an adjusted_mantissa with a negative power of 2: the caller should recompute -// in such cases. -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { - adjusted_mantissa answer; - if ((w == 0) || (q < binary::smallest_power_of_ten())) { - answer.power2 = 0; - answer.mantissa = 0; - // result should be zero - return answer; - } - if (q > binary::largest_power_of_ten()) { - // we want to get infinity: - answer.power2 = binary::infinite_power(); - answer.mantissa = 0; - return answer; - } - // At this point in time q is in [powers::smallest_power_of_five, powers::largest_power_of_five]. - - // We want the most significant bit of i to be 1. Shift if needed. - int lz = leading_zeroes(w); - w <<= lz; - - // The required precision is binary::mantissa_explicit_bits() + 3 because - // 1. We need the implicit bit - // 2. We need an extra bit for rounding purposes - // 3. We might lose a bit due to the "upperbit" routine (result too small, requiring a shift) - - value128 product = compute_product_approximation(q, w); - // The computed 'product' is always sufficient. - // Mathematical proof: - // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to appear) - // See script/mushtak_lemire.py - - // The "compute_product_approximation" function can be slightly slower than a branchless approach: - // value128 product = compute_product(q, w); - // but in practice, we can win big with the compute_product_approximation if its additional branch - // is easily predicted. Which is best is data specific. - int upperbit = int(product.high >> 63); - - answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); - - answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - binary::minimum_exponent()); - if (answer.power2 <= 0) { // we have a subnormal? - // Here have that answer.power2 <= 0 so -answer.power2 >= 0 - if(-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. - answer.power2 = 0; - answer.mantissa = 0; - // result should be zero - return answer; - } - // next line is safe because -answer.power2 + 1 < 64 - answer.mantissa >>= -answer.power2 + 1; - // Thankfully, we can't have both "round-to-even" and subnormals because - // "round-to-even" only occurs for powers close to 0. - answer.mantissa += (answer.mantissa & 1); // round up - answer.mantissa >>= 1; - // There is a weird scenario where we don't have a subnormal but just. - // Suppose we start with 2.2250738585072013e-308, we end up - // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal - // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round - // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer - // subnormal, but we can only know this after rounding. - // So we only declare a subnormal if we are smaller than the threshold. - answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1; - return answer; - } - - // usually, we round *up*, but if we fall right in between and and we have an - // even basis, we need to round down - // We are only concerned with the cases where 5**q fits in single 64-bit word. - if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) && - ((answer.mantissa & 3) == 1) ) { // we may fall between two floats! - // To be in-between two floats we need that in doing - // answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); - // ... we dropped out only zeroes. But if this happened, then we can go back!!! - if((answer.mantissa << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == product.high) { - answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up - } - } - - answer.mantissa += (answer.mantissa & 1); // round up - answer.mantissa >>= 1; - if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { - answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); - answer.power2++; // undo previous addition - } - - answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); - if (answer.power2 >= binary::infinite_power()) { // infinity - answer.power2 = binary::infinite_power(); - answer.mantissa = 0; - } - return answer; -} - -} // namespace fast_float - -#endif - -#ifndef FASTFLOAT_BIGINT_H -#define FASTFLOAT_BIGINT_H - -#include -#include -#include -#include - - -namespace fast_float { - -// the limb width: we want efficient multiplication of double the bits in -// limb, or for 64-bit limbs, at least 64-bit multiplication where we can -// extract the high and low parts efficiently. this is every 64-bit -// architecture except for sparc, which emulates 128-bit multiplication. -// we might have platforms where `CHAR_BIT` is not 8, so let's avoid -// doing `8 * sizeof(limb)`. -#if defined(FASTFLOAT_64BIT) && !defined(__sparc) -#define FASTFLOAT_64BIT_LIMB 1 -typedef uint64_t limb; -constexpr size_t limb_bits = 64; -#else -#define FASTFLOAT_32BIT_LIMB -typedef uint32_t limb; -constexpr size_t limb_bits = 32; -#endif - -typedef span limb_span; - -// number of bits in a bigint. this needs to be at least the number -// of bits required to store the largest bigint, which is -// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or -// ~3600 bits, so we round to 4000. -constexpr size_t bigint_bits = 4000; -constexpr size_t bigint_limbs = bigint_bits / limb_bits; - -// vector-like type that is allocated on the stack. the entire -// buffer is pre-allocated, and only the length changes. -template -struct stackvec { - limb data[size]; - // we never need more than 150 limbs - uint16_t length{0}; - - stackvec() = default; - stackvec(const stackvec &) = delete; - stackvec &operator=(const stackvec &) = delete; - stackvec(stackvec &&) = delete; - stackvec &operator=(stackvec &&other) = delete; - - // create stack vector from existing limb span. - FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) { - FASTFLOAT_ASSERT(try_extend(s)); - } - - FASTFLOAT_CONSTEXPR14 limb& operator[](size_t index) noexcept { - FASTFLOAT_DEBUG_ASSERT(index < length); - return data[index]; - } - FASTFLOAT_CONSTEXPR14 const limb& operator[](size_t index) const noexcept { - FASTFLOAT_DEBUG_ASSERT(index < length); - return data[index]; - } - // index from the end of the container - FASTFLOAT_CONSTEXPR14 const limb& rindex(size_t index) const noexcept { - FASTFLOAT_DEBUG_ASSERT(index < length); - size_t rindex = length - index - 1; - return data[rindex]; - } - - // set the length, without bounds checking. - FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept { - length = uint16_t(len); - } - constexpr size_t len() const noexcept { - return length; - } - constexpr bool is_empty() const noexcept { - return length == 0; - } - constexpr size_t capacity() const noexcept { - return size; - } - // append item to vector, without bounds checking - FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept { - data[length] = value; - length++; - } - // append item to vector, returning if item was added - FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept { - if (len() < capacity()) { - push_unchecked(value); - return true; - } else { - return false; - } - } - // add items to the vector, from a span, without bounds checking - FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept { - limb* ptr = data + length; - std::copy_n(s.ptr, s.len(), ptr); - set_len(len() + s.len()); - } - // try to add items to the vector, returning if items were added - FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept { - if (len() + s.len() <= capacity()) { - extend_unchecked(s); - return true; - } else { - return false; - } - } - // resize the vector, without bounds checking - // if the new size is longer than the vector, assign value to each - // appended item. - FASTFLOAT_CONSTEXPR20 - void resize_unchecked(size_t new_len, limb value) noexcept { - if (new_len > len()) { - size_t count = new_len - len(); - limb* first = data + len(); - limb* last = first + count; - ::std::fill(first, last, value); - set_len(new_len); - } else { - set_len(new_len); - } - } - // try to resize the vector, returning if the vector was resized. - FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept { - if (new_len > capacity()) { - return false; - } else { - resize_unchecked(new_len, value); - return true; - } - } - // check if any limbs are non-zero after the given index. - // this needs to be done in reverse order, since the index - // is relative to the most significant limbs. - FASTFLOAT_CONSTEXPR14 bool nonzero(size_t index) const noexcept { - while (index < len()) { - if (rindex(index) != 0) { - return true; - } - index++; - } - return false; - } - // normalize the big integer, so most-significant zero limbs are removed. - FASTFLOAT_CONSTEXPR14 void normalize() noexcept { - while (len() > 0 && rindex(0) == 0) { - length--; - } - } -}; - -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 -uint64_t empty_hi64(bool& truncated) noexcept { - truncated = false; - return 0; -} - -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -uint64_t uint64_hi64(uint64_t r0, bool& truncated) noexcept { - truncated = false; - int shl = leading_zeroes(r0); - return r0 << shl; -} - -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept { - int shl = leading_zeroes(r0); - if (shl == 0) { - truncated = r1 != 0; - return r0; - } else { - int shr = 64 - shl; - truncated = (r1 << shl) != 0; - return (r0 << shl) | (r1 >> shr); - } -} - -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept { - return uint64_hi64(r0, truncated); -} - -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -uint64_t uint32_hi64(uint32_t r0, uint32_t r1, bool& truncated) noexcept { - uint64_t x0 = r0; - uint64_t x1 = r1; - return uint64_hi64((x0 << 32) | x1, truncated); -} - -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool& truncated) noexcept { - uint64_t x0 = r0; - uint64_t x1 = r1; - uint64_t x2 = r2; - return uint64_hi64(x0, (x1 << 32) | x2, truncated); -} - -// add two small integers, checking for overflow. -// we want an efficient operation. for msvc, where -// we don't have built-in intrinsics, this is still -// pretty fast. -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -limb scalar_add(limb x, limb y, bool& overflow) noexcept { - limb z; -// gcc and clang -#if defined(__has_builtin) - #if __has_builtin(__builtin_add_overflow) - if (!cpp20_and_in_constexpr()) { - overflow = __builtin_add_overflow(x, y, &z); - return z; - } - #endif -#endif - - // generic, this still optimizes correctly on MSVC. - z = x + y; - overflow = z < x; - return z; -} - -// multiply two small integers, getting both the high and low bits. -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -limb scalar_mul(limb x, limb y, limb& carry) noexcept { -#ifdef FASTFLOAT_64BIT_LIMB - #if defined(__SIZEOF_INT128__) - // GCC and clang both define it as an extension. - __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); - carry = limb(z >> limb_bits); - return limb(z); - #else - // fallback, no native 128-bit integer multiplication with carry. - // on msvc, this optimizes identically, somehow. - value128 z = full_multiplication(x, y); - bool overflow; - z.low = scalar_add(z.low, carry, overflow); - z.high += uint64_t(overflow); // cannot overflow - carry = z.high; - return z.low; - #endif -#else - uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); - carry = limb(z >> limb_bits); - return limb(z); -#endif -} - -// add scalar value to bigint starting from offset. -// used in grade school multiplication -template -inline FASTFLOAT_CONSTEXPR20 -bool small_add_from(stackvec& vec, limb y, size_t start) noexcept { - size_t index = start; - limb carry = y; - bool overflow; - while (carry != 0 && index < vec.len()) { - vec[index] = scalar_add(vec[index], carry, overflow); - carry = limb(overflow); - index += 1; - } - if (carry != 0) { - FASTFLOAT_TRY(vec.try_push(carry)); - } - return true; -} - -// add scalar value to bigint. -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -bool small_add(stackvec& vec, limb y) noexcept { - return small_add_from(vec, y, 0); -} - -// multiply bigint by scalar value. -template -inline FASTFLOAT_CONSTEXPR20 -bool small_mul(stackvec& vec, limb y) noexcept { - limb carry = 0; - for (size_t index = 0; index < vec.len(); index++) { - vec[index] = scalar_mul(vec[index], y, carry); - } - if (carry != 0) { - FASTFLOAT_TRY(vec.try_push(carry)); - } - return true; -} - -// add bigint to bigint starting from index. -// used in grade school multiplication -template -FASTFLOAT_CONSTEXPR20 -bool large_add_from(stackvec& x, limb_span y, size_t start) noexcept { - // the effective x buffer is from `xstart..x.len()`, so exit early - // if we can't get that current range. - if (x.len() < start || y.len() > x.len() - start) { - FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); - } - - bool carry = false; - for (size_t index = 0; index < y.len(); index++) { - limb xi = x[index + start]; - limb yi = y[index]; - bool c1 = false; - bool c2 = false; - xi = scalar_add(xi, yi, c1); - if (carry) { - xi = scalar_add(xi, 1, c2); - } - x[index + start] = xi; - carry = c1 | c2; - } - - // handle overflow - if (carry) { - FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); - } - return true; -} - -// add bigint to bigint. -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -bool large_add_from(stackvec& x, limb_span y) noexcept { - return large_add_from(x, y, 0); -} - -// grade-school multiplication algorithm -template -FASTFLOAT_CONSTEXPR20 -bool long_mul(stackvec& x, limb_span y) noexcept { - limb_span xs = limb_span(x.data, x.len()); - stackvec z(xs); - limb_span zs = limb_span(z.data, z.len()); - - if (y.len() != 0) { - limb y0 = y[0]; - FASTFLOAT_TRY(small_mul(x, y0)); - for (size_t index = 1; index < y.len(); index++) { - limb yi = y[index]; - stackvec zi; - if (yi != 0) { - // re-use the same buffer throughout - zi.set_len(0); - FASTFLOAT_TRY(zi.try_extend(zs)); - FASTFLOAT_TRY(small_mul(zi, yi)); - limb_span zis = limb_span(zi.data, zi.len()); - FASTFLOAT_TRY(large_add_from(x, zis, index)); - } - } - } - - x.normalize(); - return true; -} - -// grade-school multiplication algorithm -template -FASTFLOAT_CONSTEXPR20 -bool large_mul(stackvec& x, limb_span y) noexcept { - if (y.len() == 1) { - FASTFLOAT_TRY(small_mul(x, y[0])); - } else { - FASTFLOAT_TRY(long_mul(x, y)); - } - return true; -} - -template -struct pow5_tables { - static constexpr uint32_t large_step = 135; - static constexpr uint64_t small_power_of_5[] = { - 1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL, 78125UL, 390625UL, - 1953125UL, 9765625UL, 48828125UL, 244140625UL, 1220703125UL, - 6103515625UL, 30517578125UL, 152587890625UL, 762939453125UL, - 3814697265625UL, 19073486328125UL, 95367431640625UL, 476837158203125UL, - 2384185791015625UL, 11920928955078125UL, 59604644775390625UL, - 298023223876953125UL, 1490116119384765625UL, 7450580596923828125UL, - }; -#ifdef FASTFLOAT_64BIT_LIMB - constexpr static limb large_power_of_5[] = { - 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, - 10482974169319127550UL, 198276706040285095UL}; -#else - constexpr static limb large_power_of_5[] = { - 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, - 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; -#endif -}; - -template -constexpr uint32_t pow5_tables::large_step; - -template -constexpr uint64_t pow5_tables::small_power_of_5[]; - -template -constexpr limb pow5_tables::large_power_of_5[]; - -// big integer type. implements a small subset of big integer -// arithmetic, using simple algorithms since asymptotically -// faster algorithms are slower for a small number of limbs. -// all operations assume the big-integer is normalized. -struct bigint : pow5_tables<> { - // storage of the limbs, in little-endian order. - stackvec vec; - - FASTFLOAT_CONSTEXPR20 bigint(): vec() {} - bigint(const bigint &) = delete; - bigint &operator=(const bigint &) = delete; - bigint(bigint &&) = delete; - bigint &operator=(bigint &&other) = delete; - - FASTFLOAT_CONSTEXPR20 bigint(uint64_t value): vec() { -#ifdef FASTFLOAT_64BIT_LIMB - vec.push_unchecked(value); -#else - vec.push_unchecked(uint32_t(value)); - vec.push_unchecked(uint32_t(value >> 32)); -#endif - vec.normalize(); - } - - // get the high 64 bits from the vector, and if bits were truncated. - // this is to get the significant digits for the float. - FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool& truncated) const noexcept { -#ifdef FASTFLOAT_64BIT_LIMB - if (vec.len() == 0) { - return empty_hi64(truncated); - } else if (vec.len() == 1) { - return uint64_hi64(vec.rindex(0), truncated); - } else { - uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); - truncated |= vec.nonzero(2); - return result; - } -#else - if (vec.len() == 0) { - return empty_hi64(truncated); - } else if (vec.len() == 1) { - return uint32_hi64(vec.rindex(0), truncated); - } else if (vec.len() == 2) { - return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); - } else { - uint64_t result = uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); - truncated |= vec.nonzero(3); - return result; - } -#endif - } - - // compare two big integers, returning the large value. - // assumes both are normalized. if the return value is - // negative, other is larger, if the return value is - // positive, this is larger, otherwise they are equal. - // the limbs are stored in little-endian order, so we - // must compare the limbs in ever order. - FASTFLOAT_CONSTEXPR20 int compare(const bigint& other) const noexcept { - if (vec.len() > other.vec.len()) { - return 1; - } else if (vec.len() < other.vec.len()) { - return -1; - } else { - for (size_t index = vec.len(); index > 0; index--) { - limb xi = vec[index - 1]; - limb yi = other.vec[index - 1]; - if (xi > yi) { - return 1; - } else if (xi < yi) { - return -1; - } - } - return 0; - } - } - - // shift left each limb n bits, carrying over to the new limb - // returns true if we were able to shift all the digits. - FASTFLOAT_CONSTEXPR20 bool shl_bits(size_t n) noexcept { - // Internally, for each item, we shift left by n, and add the previous - // right shifted limb-bits. - // For example, we transform (for u8) shifted left 2, to: - // b10100100 b01000010 - // b10 b10010001 b00001000 - FASTFLOAT_DEBUG_ASSERT(n != 0); - FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); - - size_t shl = n; - size_t shr = limb_bits - shl; - limb prev = 0; - for (size_t index = 0; index < vec.len(); index++) { - limb xi = vec[index]; - vec[index] = (xi << shl) | (prev >> shr); - prev = xi; - } - - limb carry = prev >> shr; - if (carry != 0) { - return vec.try_push(carry); - } - return true; - } - - // move the limbs left by `n` limbs. - FASTFLOAT_CONSTEXPR20 bool shl_limbs(size_t n) noexcept { - FASTFLOAT_DEBUG_ASSERT(n != 0); - if (n + vec.len() > vec.capacity()) { - return false; - } else if (!vec.is_empty()) { - // move limbs - limb* dst = vec.data + n; - const limb* src = vec.data; - std::copy_backward(src, src + vec.len(), dst + vec.len()); - // fill in empty limbs - limb* first = vec.data; - limb* last = first + n; - ::std::fill(first, last, 0); - vec.set_len(n + vec.len()); - return true; - } else { - return true; - } - } - - // move the limbs left by `n` bits. - FASTFLOAT_CONSTEXPR20 bool shl(size_t n) noexcept { - size_t rem = n % limb_bits; - size_t div = n / limb_bits; - if (rem != 0) { - FASTFLOAT_TRY(shl_bits(rem)); - } - if (div != 0) { - FASTFLOAT_TRY(shl_limbs(div)); - } - return true; - } - - // get the number of leading zeros in the bigint. - FASTFLOAT_CONSTEXPR20 int ctlz() const noexcept { - if (vec.is_empty()) { - return 0; - } else { -#ifdef FASTFLOAT_64BIT_LIMB - return leading_zeroes(vec.rindex(0)); -#else - // no use defining a specialized leading_zeroes for a 32-bit type. - uint64_t r0 = vec.rindex(0); - return leading_zeroes(r0 << 32); -#endif - } - } - - // get the number of bits in the bigint. - FASTFLOAT_CONSTEXPR20 int bit_length() const noexcept { - int lz = ctlz(); - return int(limb_bits * vec.len()) - lz; - } - - FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { - return small_mul(vec, y); - } - - FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { - return small_add(vec, y); - } - - // multiply as if by 2 raised to a power. - FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { - return shl(exp); - } - - // multiply as if by 5 raised to a power. - FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept { - // multiply by a power of 5 - size_t large_length = sizeof(large_power_of_5) / sizeof(limb); - limb_span large = limb_span(large_power_of_5, large_length); - while (exp >= large_step) { - FASTFLOAT_TRY(large_mul(vec, large)); - exp -= large_step; - } -#ifdef FASTFLOAT_64BIT_LIMB - uint32_t small_step = 27; - limb max_native = 7450580596923828125UL; -#else - uint32_t small_step = 13; - limb max_native = 1220703125U; -#endif - while (exp >= small_step) { - FASTFLOAT_TRY(small_mul(vec, max_native)); - exp -= small_step; - } - if (exp != 0) { - // Work around clang bug https://godbolt.org/z/zedh7rrhc - // This is similar to https://github.com/llvm/llvm-project/issues/47746, - // except the workaround described there don't work here - FASTFLOAT_TRY( - small_mul(vec, limb(((void)small_power_of_5[0], small_power_of_5[exp]))) - ); - } - - return true; - } - - // multiply as if by 10 raised to a power. - FASTFLOAT_CONSTEXPR20 bool pow10(uint32_t exp) noexcept { - FASTFLOAT_TRY(pow5(exp)); - return pow2(exp); - } -}; - -} // namespace fast_float - -#endif - -#ifndef FASTFLOAT_DIGIT_COMPARISON_H -#define FASTFLOAT_DIGIT_COMPARISON_H - -#include -#include -#include -#include - - -namespace fast_float { - -// 1e0 to 1e19 -constexpr static uint64_t powers_of_ten_uint64[] = { - 1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL, - 1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL, - 100000000000000UL, 1000000000000000UL, 10000000000000000UL, 100000000000000000UL, - 1000000000000000000UL, 10000000000000000000UL}; - -// calculate the exponent, in scientific notation, of the number. -// this algorithm is not even close to optimized, but it has no practical -// effect on performance: in order to have a faster algorithm, we'd need -// to slow down performance for faster algorithms, and this is still fast. -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 -int32_t scientific_exponent(parsed_number_string_t & num) noexcept { - uint64_t mantissa = num.mantissa; - int32_t exponent = int32_t(num.exponent); - while (mantissa >= 10000) { - mantissa /= 10000; - exponent += 4; - } - while (mantissa >= 100) { - mantissa /= 100; - exponent += 2; - } - while (mantissa >= 10) { - mantissa /= 10; - exponent += 1; - } - return exponent; -} - -// this converts a native floating-point number to an extended-precision float. -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -adjusted_mantissa to_extended(T value) noexcept { - using equiv_uint = typename binary_format::equiv_uint; - constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); - constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); - constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); - - adjusted_mantissa am; - int32_t bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); - equiv_uint bits; -#if FASTFLOAT_HAS_BIT_CAST - bits = std::bit_cast(value); -#else - ::memcpy(&bits, &value, sizeof(T)); -#endif - if ((bits & exponent_mask) == 0) { - // denormal - am.power2 = 1 - bias; - am.mantissa = bits & mantissa_mask; - } else { - // normal - am.power2 = int32_t((bits & exponent_mask) >> binary_format::mantissa_explicit_bits()); - am.power2 -= bias; - am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; - } - - return am; -} - -// get the extended precision value of the halfway point between b and b+u. -// we are given a native float that represents b, so we need to adjust it -// halfway between b and b+u. -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -adjusted_mantissa to_extended_halfway(T value) noexcept { - adjusted_mantissa am = to_extended(value); - am.mantissa <<= 1; - am.mantissa += 1; - am.power2 -= 1; - return am; -} - -// round an extended-precision float to the nearest machine float. -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 -void round(adjusted_mantissa& am, callback cb) noexcept { - int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; - if (-am.power2 >= mantissa_shift) { - // have a denormal float - int32_t shift = -am.power2 + 1; - cb(am, std::min(shift, 64)); - // check for round-up: if rounding-nearest carried us to the hidden bit. - am.power2 = (am.mantissa < (uint64_t(1) << binary_format::mantissa_explicit_bits())) ? 0 : 1; - return; - } - - // have a normal float, use the default shift. - cb(am, mantissa_shift); - - // check for carry - if (am.mantissa >= (uint64_t(2) << binary_format::mantissa_explicit_bits())) { - am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); - am.power2++; - } - - // check for infinite: we could have carried to an infinite power - am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); - if (am.power2 >= binary_format::infinite_power()) { - am.power2 = binary_format::infinite_power(); - am.mantissa = 0; - } -} - -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 -void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept { - const uint64_t mask - = (shift == 64) - ? UINT64_MAX - : (uint64_t(1) << shift) - 1; - const uint64_t halfway - = (shift == 0) - ? 0 - : uint64_t(1) << (shift - 1); - uint64_t truncated_bits = am.mantissa & mask; - bool is_above = truncated_bits > halfway; - bool is_halfway = truncated_bits == halfway; - - // shift digits into position - if (shift == 64) { - am.mantissa = 0; - } else { - am.mantissa >>= shift; - } - am.power2 += shift; - - bool is_odd = (am.mantissa & 1) == 1; - am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); -} - -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 -void round_down(adjusted_mantissa& am, int32_t shift) noexcept { - if (shift == 64) { - am.mantissa = 0; - } else { - am.mantissa >>= shift; - } - am.power2 += shift; -} -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -void skip_zeros(UC const * & first, UC const * last) noexcept { - uint64_t val; - while (!cpp20_and_in_constexpr() && std::distance(first, last) >= int_cmp_len()) { - ::memcpy(&val, first, sizeof(uint64_t)); - if (val != int_cmp_zeros()) { - break; - } - first += int_cmp_len(); - } - while (first != last) { - if (*first != UC('0')) { - break; - } - first++; - } -} - -// determine if any non-zero digits were truncated. -// all characters must be valid digits. -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -bool is_truncated(UC const * first, UC const * last) noexcept { - // do 8-bit optimizations, can just compare to 8 literal 0s. - uint64_t val; - while (!cpp20_and_in_constexpr() && std::distance(first, last) >= int_cmp_len()) { - ::memcpy(&val, first, sizeof(uint64_t)); - if (val != int_cmp_zeros()) { - return true; - } - first += int_cmp_len(); - } - while (first != last) { - if (*first != UC('0')) { - return true; - } - ++first; - } - return false; -} -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -bool is_truncated(span s) noexcept { - return is_truncated(s.ptr, s.ptr + s.len()); -} - - -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -void parse_eight_digits(const UC*& p, limb& value, size_t& counter, size_t& count) noexcept { - value = value * 100000000 + parse_eight_digits_unrolled(p); - p += 8; - counter += 8; - count += 8; -} - -template -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 -void parse_one_digit(UC const *& p, limb& value, size_t& counter, size_t& count) noexcept { - value = value * 10 + limb(*p - UC('0')); - p++; - counter++; - count++; -} - -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -void add_native(bigint& big, limb power, limb value) noexcept { - big.mul(power); - big.add(value); -} - -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 -void round_up_bigint(bigint& big, size_t& count) noexcept { - // need to round-up the digits, but need to avoid rounding - // ....9999 to ...10000, which could cause a false halfway point. - add_native(big, 10, 1); - count++; -} - -// parse the significant digits into a big integer -template -inline FASTFLOAT_CONSTEXPR20 -void parse_mantissa(bigint& result, parsed_number_string_t& num, size_t max_digits, size_t& digits) noexcept { - // try to minimize the number of big integer and scalar multiplication. - // therefore, try to parse 8 digits at a time, and multiply by the largest - // scalar value (9 or 19 digits) for each step. - size_t counter = 0; - digits = 0; - limb value = 0; -#ifdef FASTFLOAT_64BIT_LIMB - size_t step = 19; -#else - size_t step = 9; -#endif - - // process all integer digits. - UC const * p = num.integer.ptr; - UC const * pend = p + num.integer.len(); - skip_zeros(p, pend); - // process all digits, in increments of step per loop - while (p != pend) { - while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { - parse_eight_digits(p, value, counter, digits); - } - while (counter < step && p != pend && digits < max_digits) { - parse_one_digit(p, value, counter, digits); - } - if (digits == max_digits) { - // add the temporary value, then check if we've truncated any digits - add_native(result, limb(powers_of_ten_uint64[counter]), value); - bool truncated = is_truncated(p, pend); - if (num.fraction.ptr != nullptr) { - truncated |= is_truncated(num.fraction); - } - if (truncated) { - round_up_bigint(result, digits); - } - return; - } else { - add_native(result, limb(powers_of_ten_uint64[counter]), value); - counter = 0; - value = 0; - } - } - - // add our fraction digits, if they're available. - if (num.fraction.ptr != nullptr) { - p = num.fraction.ptr; - pend = p + num.fraction.len(); - if (digits == 0) { - skip_zeros(p, pend); - } - // process all digits, in increments of step per loop - while (p != pend) { - while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { - parse_eight_digits(p, value, counter, digits); - } - while (counter < step && p != pend && digits < max_digits) { - parse_one_digit(p, value, counter, digits); - } - if (digits == max_digits) { - // add the temporary value, then check if we've truncated any digits - add_native(result, limb(powers_of_ten_uint64[counter]), value); - bool truncated = is_truncated(p, pend); - if (truncated) { - round_up_bigint(result, digits); - } - return; - } else { - add_native(result, limb(powers_of_ten_uint64[counter]), value); - counter = 0; - value = 0; - } - } - } - - if (counter != 0) { - add_native(result, limb(powers_of_ten_uint64[counter]), value); - } -} - -template -inline FASTFLOAT_CONSTEXPR20 -adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcept { - FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); - adjusted_mantissa answer; - bool truncated; - answer.mantissa = bigmant.hi64(truncated); - int bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); - answer.power2 = bigmant.bit_length() - 64 + bias; - - round(answer, [truncated](adjusted_mantissa& a, int32_t shift) { - round_nearest_tie_even(a, shift, [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { - return is_above || (is_halfway && truncated) || (is_odd && is_halfway); - }); - }); - - return answer; -} - -// the scaling here is quite simple: we have, for the real digits `m * 10^e`, -// and for the theoretical digits `n * 2^f`. Since `e` is always negative, -// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. -// we then need to scale by `2^(f- e)`, and then the two significant digits -// are of the same magnitude. -template -inline FASTFLOAT_CONSTEXPR20 -adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int32_t exponent) noexcept { - bigint& real_digits = bigmant; - int32_t real_exp = exponent; - - // get the value of `b`, rounded down, and get a bigint representation of b+h - adjusted_mantissa am_b = am; - // gcc7 buf: use a lambda to remove the noexcept qualifier bug with -Wnoexcept-type. - round(am_b, [](adjusted_mantissa&a, int32_t shift) { round_down(a, shift); }); - T b; - to_float(false, am_b, b); - adjusted_mantissa theor = to_extended_halfway(b); - bigint theor_digits(theor.mantissa); - int32_t theor_exp = theor.power2; - - // scale real digits and theor digits to be same power. - int32_t pow2_exp = theor_exp - real_exp; - uint32_t pow5_exp = uint32_t(-real_exp); - if (pow5_exp != 0) { - FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); - } - if (pow2_exp > 0) { - FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); - } else if (pow2_exp < 0) { - FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); - } - - // compare digits, and use it to director rounding - int ord = real_digits.compare(theor_digits); - adjusted_mantissa answer = am; - round(answer, [ord](adjusted_mantissa& a, int32_t shift) { - round_nearest_tie_even(a, shift, [ord](bool is_odd, bool _, bool __) -> bool { - (void)_; // not needed, since we've done our comparison - (void)__; // not needed, since we've done our comparison - if (ord > 0) { - return true; - } else if (ord < 0) { - return false; - } else { - return is_odd; - } - }); - }); - - return answer; -} - -// parse the significant digits as a big integer to unambiguously round the -// the significant digits. here, we are trying to determine how to round -// an extended float representation close to `b+h`, halfway between `b` -// (the float rounded-down) and `b+u`, the next positive float. this -// algorithm is always correct, and uses one of two approaches. when -// the exponent is positive relative to the significant digits (such as -// 1234), we create a big-integer representation, get the high 64-bits, -// determine if any lower bits are truncated, and use that to direct -// rounding. in case of a negative exponent relative to the significant -// digits (such as 1.2345), we create a theoretical representation of -// `b` as a big-integer type, scaled to the same binary exponent as -// the actual digits. we then compare the big integer representations -// of both, and use that to direct rounding. -template -inline FASTFLOAT_CONSTEXPR20 -adjusted_mantissa digit_comp(parsed_number_string_t& num, adjusted_mantissa am) noexcept { - // remove the invalid exponent bias - am.power2 -= invalid_am_bias; - - int32_t sci_exp = scientific_exponent(num); - size_t max_digits = binary_format::max_digits(); - size_t digits = 0; - bigint bigmant; - parse_mantissa(bigmant, num, max_digits, digits); - // can't underflow, since digits is at most max_digits. - int32_t exponent = sci_exp + 1 - int32_t(digits); - if (exponent >= 0) { - return positive_digit_comp(bigmant, exponent); - } else { - return negative_digit_comp(bigmant, am, exponent); - } -} - -} // namespace fast_float - -#endif - -#ifndef FASTFLOAT_PARSE_NUMBER_H -#define FASTFLOAT_PARSE_NUMBER_H - - -#include -#include -#include -#include -namespace fast_float { - - -namespace detail { -/** - * Special case +inf, -inf, nan, infinity, -infinity. - * The case comparisons could be made much faster given that we know that the - * strings a null-free and fixed. - **/ -template -from_chars_result_t FASTFLOAT_CONSTEXPR14 -parse_infnan(UC const * first, UC const * last, T &value) noexcept { - from_chars_result_t answer{}; - answer.ptr = first; - answer.ec = std::errc(); // be optimistic - bool minusSign = false; - if (*first == UC('-')) { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here - minusSign = true; - ++first; - } -#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default - if (*first == UC('+')) { - ++first; - } -#endif - if (last - first >= 3) { - if (fastfloat_strncasecmp(first, str_const_nan(), 3)) { - answer.ptr = (first += 3); - value = minusSign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); - // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). - if(first != last && *first == UC('(')) { - for(UC const * ptr = first + 1; ptr != last; ++ptr) { - if (*ptr == UC(')')) { - answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) - break; - } - else if(!((UC('a') <= *ptr && *ptr <= UC('z')) || (UC('A') <= *ptr && *ptr <= UC('Z')) || (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) - break; // forbidden char, not nan(n-char-seq-opt) - } - } - return answer; - } - if (fastfloat_strncasecmp(first, str_const_inf(), 3)) { - if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, str_const_inf() + 3, 5)) { - answer.ptr = first + 8; - } else { - answer.ptr = first + 3; - } - value = minusSign ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); - return answer; - } - } - answer.ec = std::errc::invalid_argument; - return answer; -} - -/** - * Returns true if the floating-pointing rounding mode is to 'nearest'. - * It is the default on most system. This function is meant to be inexpensive. - * Credit : @mwalcott3 - */ -fastfloat_really_inline bool rounds_to_nearest() noexcept { - // https://lemire.me/blog/2020/06/26/gcc-not-nearest/ -#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) - return false; -#endif - // See - // A fast function to check your floating-point rounding mode - // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ - // - // This function is meant to be equivalent to : - // prior: #include - // return fegetround() == FE_TONEAREST; - // However, it is expected to be much faster than the fegetround() - // function call. - // - // The volatile keywoard prevents the compiler from computing the function - // at compile-time. - // There might be other ways to prevent compile-time optimizations (e.g., asm). - // The value does not need to be std::numeric_limits::min(), any small - // value so that 1 + x should round to 1 would do (after accounting for excess - // precision, as in 387 instructions). - static volatile float fmin = std::numeric_limits::min(); - float fmini = fmin; // we copy it so that it gets loaded at most once. - // - // Explanation: - // Only when fegetround() == FE_TONEAREST do we have that - // fmin + 1.0f == 1.0f - fmin. - // - // FE_UPWARD: - // fmin + 1.0f > 1 - // 1.0f - fmin == 1 - // - // FE_DOWNWARD or FE_TOWARDZERO: - // fmin + 1.0f == 1 - // 1.0f - fmin < 1 - // - // Note: This may fail to be accurate if fast-math has been - // enabled, as rounding conventions may not apply. - #ifdef FASTFLOAT_VISUAL_STUDIO - # pragma warning(push) - // todo: is there a VS warning? - // see https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 - #elif defined(__clang__) - # pragma clang diagnostic push - # pragma clang diagnostic ignored "-Wfloat-equal" - #elif defined(__GNUC__) - # pragma GCC diagnostic push - # pragma GCC diagnostic ignored "-Wfloat-equal" - #endif - return (fmini + 1.0f == 1.0f - fmini); - #ifdef FASTFLOAT_VISUAL_STUDIO - # pragma warning(pop) - #elif defined(__clang__) - # pragma clang diagnostic pop - #elif defined(__GNUC__) - # pragma GCC diagnostic pop - #endif -} - -} // namespace detail - -template -struct from_chars_caller -{ - template - FASTFLOAT_CONSTEXPR20 - static from_chars_result_t call(UC const * first, UC const * last, - T &value, parse_options_t options) noexcept { - return from_chars_advanced(first, last, value, options); - } -}; - -#if __STDCPP_FLOAT32_T__ == 1 -template <> -struct from_chars_caller -{ - template - FASTFLOAT_CONSTEXPR20 - static from_chars_result_t call(UC const * first, UC const * last, - std::float32_t &value, parse_options_t options) noexcept{ - // if std::float32_t is defined, and we are in C++23 mode; macro set for float32; - // set value to float due to equivalence between float and float32_t - float val; - auto ret = from_chars_advanced(first, last, val, options); - value = val; - return ret; - } -}; -#endif - -#if __STDCPP_FLOAT64_T__ == 1 -template <> -struct from_chars_caller -{ - template - FASTFLOAT_CONSTEXPR20 - static from_chars_result_t call(UC const * first, UC const * last, - std::float64_t &value, parse_options_t options) noexcept{ - // if std::float64_t is defined, and we are in C++23 mode; macro set for float64; - // set value as double due to equivalence between double and float64_t - double val; - auto ret = from_chars_advanced(first, last, val, options); - value = val; - return ret; - } -}; -#endif - - -template -FASTFLOAT_CONSTEXPR20 -from_chars_result_t from_chars(UC const * first, UC const * last, - T &value, chars_format fmt /*= chars_format::general*/) noexcept { - return from_chars_caller::call(first, last, value, parse_options_t(fmt)); -} - -template -FASTFLOAT_CONSTEXPR20 -from_chars_result_t from_chars_advanced(UC const * first, UC const * last, - T &value, parse_options_t options) noexcept { - - static_assert (is_supported_float_type(), "only some floating-point types are supported"); - static_assert (is_supported_char_type(), "only char, wchar_t, char16_t and char32_t are supported"); - - from_chars_result_t answer; -#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default - while ((first != last) && fast_float::is_space(uint8_t(*first))) { - first++; - } -#endif - if (first == last) { - answer.ec = std::errc::invalid_argument; - answer.ptr = first; - return answer; - } - parsed_number_string_t pns = parse_number_string(first, last, options); - if (!pns.valid) { - if (options.format & chars_format::no_infnan) { - answer.ec = std::errc::invalid_argument; - answer.ptr = first; - return answer; - } else { - return detail::parse_infnan(first, last, value); - } - } - - answer.ec = std::errc(); // be optimistic - answer.ptr = pns.lastmatch; - // The implementation of the Clinger's fast path is convoluted because - // we want round-to-nearest in all cases, irrespective of the rounding mode - // selected on the thread. - // We proceed optimistically, assuming that detail::rounds_to_nearest() returns - // true. - if (binary_format::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format::max_exponent_fast_path() && !pns.too_many_digits) { - // Unfortunately, the conventional Clinger's fast path is only possible - // when the system rounds to the nearest float. - // - // We expect the next branch to almost always be selected. - // We could check it first (before the previous branch), but - // there might be performance advantages at having the check - // be last. - if(!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { - // We have that fegetround() == FE_TONEAREST. - // Next is Clinger's fast path. - if (pns.mantissa <=binary_format::max_mantissa_fast_path()) { - value = T(pns.mantissa); - if (pns.exponent < 0) { value = value / binary_format::exact_power_of_ten(-pns.exponent); } - else { value = value * binary_format::exact_power_of_ten(pns.exponent); } - if (pns.negative) { value = -value; } - return answer; - } - } else { - // We do not have that fegetround() == FE_TONEAREST. - // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's proposal - if (pns.exponent >= 0 && pns.mantissa <=binary_format::max_mantissa_fast_path(pns.exponent)) { -#if defined(__clang__) || defined(FASTFLOAT_32BIT) - // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD - if(pns.mantissa == 0) { - value = pns.negative ? T(-0.) : T(0.); - return answer; - } -#endif - value = T(pns.mantissa) * binary_format::exact_power_of_ten(pns.exponent); - if (pns.negative) { value = -value; } - return answer; - } - } - } - adjusted_mantissa am = compute_float>(pns.exponent, pns.mantissa); - if(pns.too_many_digits && am.power2 >= 0) { - if(am != compute_float>(pns.exponent, pns.mantissa + 1)) { - am = compute_error>(pns.exponent, pns.mantissa); - } - } - // If we called compute_float>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0), - // then we need to go the long way around again. This is very uncommon. - if(am.power2 < 0) { am = digit_comp(pns, am); } - to_float(pns.negative, am, value); - // Test for over/underflow. - if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || am.power2 == binary_format::infinite_power()) { - answer.ec = std::errc::result_out_of_range; - } - return answer; -} - - -template -FASTFLOAT_CONSTEXPR20 -from_chars_result_t from_chars(UC const* first, UC const* last, T& value, int base) noexcept { - static_assert (is_supported_char_type(), "only char, wchar_t, char16_t and char32_t are supported"); - - from_chars_result_t answer; -#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default - while ((first != last) && fast_float::is_space(uint8_t(*first))) { - first++; - } -#endif - if (first == last || base < 2 || base > 36) { - answer.ec = std::errc::invalid_argument; - answer.ptr = first; - return answer; - } - return parse_int_string(first, last, value, base); -} - -} // namespace fast_float - -#endif - diff --git a/Include/Pipe/Extern/portable-file-dialogs.h b/Include/Pipe/Extern/portable-file-dialogs.h deleted file mode 100644 index 1fc79a29..00000000 --- a/Include/Pipe/Extern/portable-file-dialogs.h +++ /dev/null @@ -1,1887 +0,0 @@ -// -// Portable File Dialogs -// -// Copyright © 2018–2022 Sam Hocevar -// -// This library is free software. It comes without any warranty, to -// the extent permitted by applicable law. You can redistribute it -// and/or modify it under the terms of the Do What the Fuck You Want -// to Public License, Version 2, as published by the WTFPL Task Force. -// See http://www.wtfpl.net/ for more details. -// - -#pragma once - -#if _WIN32 -#ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN 1 -#endif -#include -#include -#include -#include // IFileDialog -#include -#include -#include // std::async -#include // GetUserProfileDirectory() - -#elif __EMSCRIPTEN__ -#include - -#else -#ifndef _POSIX_C_SOURCE -# define _POSIX_C_SOURCE 2 // for popen() -#endif -#ifdef __APPLE__ -# ifndef _DARWIN_C_SOURCE -# define _DARWIN_C_SOURCE -# endif -#endif -#include // popen() -#include // std::getenv() -#include // fcntl() -#include // read(), pipe(), dup2(), getuid() -#include // ::kill, std::signal -#include // stat() -#include // waitpid() -#include // getpwnam() -#endif - -#include // std::string -#include // std::shared_ptr -#include // std::ostream -#include // std::map -#include // std::set -#include // std::regex -#include // std::mutex, std::this_thread -#include // std::chrono - -// Versions of mingw64 g++ up to 9.3.0 do not have a complete IFileDialog -#ifndef PFD_HAS_IFILEDIALOG -# define PFD_HAS_IFILEDIALOG 1 -# if (defined __MINGW64__ || defined __MINGW32__) && defined __GXX_ABI_VERSION -# if __GXX_ABI_VERSION <= 1013 -# undef PFD_HAS_IFILEDIALOG -# define PFD_HAS_IFILEDIALOG 0 -# endif -# endif -#endif - -namespace pfd -{ - -enum class button -{ - cancel = -1, - ok, - yes, - no, - abort, - retry, - ignore, -}; - -enum class choice -{ - ok = 0, - ok_cancel, - yes_no, - yes_no_cancel, - retry_cancel, - abort_retry_ignore, -}; - -enum class icon -{ - info = 0, - warning, - error, - question, -}; - -// Additional option flags for various dialog constructors -enum class opt : uint8_t -{ - none = 0, - // For file open, allow multiselect. - multiselect = 0x1, - // For file save, force overwrite and disable the confirmation dialog. - force_overwrite = 0x2, - // For folder select, force path to be the provided argument instead - // of the last opened directory, which is the Microsoft-recommended, - // user-friendly behaviour. - force_path = 0x4, -}; - -inline opt operator |(opt a, opt b) { return opt(uint8_t(a) | uint8_t(b)); } -inline bool operator &(opt a, opt b) { return bool(uint8_t(a) & uint8_t(b)); } - -// The settings class, only exposing to the user a way to set verbose mode -// and to force a rescan of installed desktop helpers (zenity, kdialog…). -class settings -{ -public: - static bool available(); - - static void verbose(bool value); - static void rescan(); - -protected: - explicit settings(bool resync = false); - - bool check_program(std::string const &program); - - inline bool is_osascript() const; - inline bool is_zenity() const; - inline bool is_kdialog() const; - - enum class flag - { - is_scanned = 0, - is_verbose, - - has_zenity, - has_matedialog, - has_qarma, - has_kdialog, - is_vista, - - max_flag, - }; - - // Static array of flags for internal state - bool const &flags(flag in_flag) const; - - // Non-const getter for the static array of flags - bool &flags(flag in_flag); -}; - -// Internal classes, not to be used by client applications -namespace internal -{ - -// Process wait timeout, in milliseconds -static int const default_wait_timeout = 20; - -class executor -{ - friend class dialog; - -public: - // High level function to get the result of a command - std::string result(int *exit_code = nullptr); - - // High level function to abort - bool kill(); - -#if _WIN32 - void start_func(std::function const &fun); - static BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam); -#elif __EMSCRIPTEN__ - void start(int exit_code); -#else - void start_process(std::vector const &command); -#endif - - ~executor(); - -protected: - bool ready(int timeout = default_wait_timeout); - void stop(); - -private: - bool m_running = false; - std::string m_stdout; - int m_exit_code = -1; -#if _WIN32 - std::future m_future; - std::set m_windows; - std::condition_variable m_cond; - std::mutex m_mutex; - DWORD m_tid; -#elif __EMSCRIPTEN__ || __NX__ - // FIXME: do something -#else - pid_t m_pid = 0; - int m_fd = -1; -#endif -}; - -class platform -{ -protected: -#if _WIN32 - // Helper class around LoadLibraryA() and GetProcAddress() with some safety - class dll - { - public: - dll(std::string const &name); - ~dll(); - - template class proc - { - public: - proc(dll const &lib, std::string const &sym) - : m_proc(reinterpret_cast((void *)::GetProcAddress(lib.handle, sym.c_str()))) - {} - - operator bool() const { return m_proc != nullptr; } - operator T *() const { return m_proc; } - - private: - T *m_proc; - }; - - private: - HMODULE handle; - }; - - // Helper class around CoInitialize() and CoUnInitialize() - class ole32_dll : public dll - { - public: - ole32_dll(); - ~ole32_dll(); - bool is_initialized(); - - private: - HRESULT m_state; - }; - - // Helper class around CreateActCtx() and ActivateActCtx() - class new_style_context - { - public: - new_style_context(); - ~new_style_context(); - - private: - HANDLE create(); - ULONG_PTR m_cookie = 0; - }; -#endif -}; - -class dialog : protected settings, protected platform -{ -public: - bool ready(int timeout = default_wait_timeout) const; - bool kill() const; - -protected: - explicit dialog(); - - std::vector desktop_helper() const; - static std::string buttons_to_name(choice _choice); - static std::string get_icon_name(icon _icon); - - std::string powershell_quote(std::string const &str) const; - std::string osascript_quote(std::string const &str) const; - std::string shell_quote(std::string const &str) const; - - // Keep handle to executing command - std::shared_ptr m_async; -}; - -class file_dialog : public dialog -{ -protected: - enum type - { - open, - save, - folder, - }; - - file_dialog(type in_type, - std::string const &title, - std::string const &default_path = "", - std::vector const &filters = {}, - opt options = opt::none); - -protected: - std::string string_result(); - std::vector vector_result(); - -#if _WIN32 - static int CALLBACK bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData); -#if PFD_HAS_IFILEDIALOG - std::string select_folder_vista(IFileDialog *ifd, bool force_path); -#endif - - std::wstring m_wtitle; - std::wstring m_wdefault_path; - - std::vector m_vector_result; -#endif -}; - -} // namespace internal - -// -// The path class provides some platform-specific path constants -// - -class path : protected internal::platform -{ -public: - static std::string home(); - static std::string separator(); -}; - -// -// The notify widget -// - -class notify : public internal::dialog -{ -public: - notify(std::string const &title, - std::string const &message, - icon _icon = icon::info); -}; - -// -// The message widget -// - -class message : public internal::dialog -{ -public: - message(std::string const &title, - std::string const &text, - choice _choice = choice::ok_cancel, - icon _icon = icon::info); - - button result(); - -private: - // Some extra logic to map the exit code to button number - std::map m_mappings; -}; - -// -// The open_file, save_file, and open_folder widgets -// - -class open_file : public internal::file_dialog -{ -public: - open_file(std::string const &title, - std::string const &default_path = "", - std::vector const &filters = { "All Files", "*" }, - opt options = opt::none); - -#if defined(__has_cpp_attribute) -#if __has_cpp_attribute(deprecated) - // Backwards compatibility - [[deprecated("Use pfd::opt::multiselect instead of allow_multiselect")]] -#endif -#endif - open_file(std::string const &title, - std::string const &default_path, - std::vector const &filters, - bool allow_multiselect); - - std::vector result(); -}; - -class save_file : public internal::file_dialog -{ -public: - save_file(std::string const &title, - std::string const &default_path = "", - std::vector const &filters = { "All Files", "*" }, - opt options = opt::none); - -#if defined(__has_cpp_attribute) -#if __has_cpp_attribute(deprecated) - // Backwards compatibility - [[deprecated("Use pfd::opt::force_overwrite instead of confirm_overwrite")]] -#endif -#endif - save_file(std::string const &title, - std::string const &default_path, - std::vector const &filters, - bool confirm_overwrite); - - std::string result(); -}; - -class select_folder : public internal::file_dialog -{ -public: - select_folder(std::string const &title, - std::string const &default_path = "", - opt options = opt::none); - - std::string result(); -}; - -// -// Below this are all the method implementations. You may choose to define the -// macro PFD_SKIP_IMPLEMENTATION everywhere before including this header except -// in one place. This may reduce compilation times. -// - -#if !defined PFD_SKIP_IMPLEMENTATION - -// internal free functions implementations - -namespace internal -{ - -#if _WIN32 -static inline std::wstring str2wstr(std::string const &str) -{ - int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0); - std::wstring ret(len, '\0'); - MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPWSTR)ret.data(), (int)ret.size()); - return ret; -} - -static inline std::string wstr2str(std::wstring const &str) -{ - int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0, nullptr, nullptr); - std::string ret(len, '\0'); - WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPSTR)ret.data(), (int)ret.size(), nullptr, nullptr); - return ret; -} - -static inline bool is_vista() -{ - OSVERSIONINFOEXW osvi; - memset(&osvi, 0, sizeof(osvi)); - DWORDLONG const mask = VerSetConditionMask( - VerSetConditionMask( - VerSetConditionMask( - 0, VER_MAJORVERSION, VER_GREATER_EQUAL), - VER_MINORVERSION, VER_GREATER_EQUAL), - VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - osvi.dwOSVersionInfoSize = sizeof(osvi); - osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA); - osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA); - osvi.wServicePackMajor = 0; - - return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, mask) != FALSE; -} -#endif - -// This is necessary until C++20 which will have std::string::ends_with() etc. - -static inline bool ends_with(std::string const &str, std::string const &suffix) -{ - return suffix.size() <= str.size() && - str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; -} - -static inline bool starts_with(std::string const &str, std::string const &prefix) -{ - return prefix.size() <= str.size() && - str.compare(0, prefix.size(), prefix) == 0; -} - -// This is necessary until C++17 which will have std::filesystem::is_directory - -static inline bool is_directory(std::string const &path) -{ -#if _WIN32 - auto attr = GetFileAttributesA(path.c_str()); - return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY); -#elif __EMSCRIPTEN__ - // TODO - return false; -#else - struct stat s; - return stat(path.c_str(), &s) == 0 && S_ISDIR(s.st_mode); -#endif -} - -// This is necessary because getenv is not thread-safe - -static inline std::string getenv(std::string const &str) -{ -#if _MSC_VER - char *buf = nullptr; - size_t size = 0; - if (_dupenv_s(&buf, &size, str.c_str()) == 0 && buf) - { - std::string ret(buf); - free(buf); - return ret; - } - return ""; -#else - auto buf = std::getenv(str.c_str()); - return buf ? buf : ""; -#endif -} - -} // namespace internal - -// settings implementation - -inline settings::settings(bool resync) -{ - flags(flag::is_scanned) &= !resync; - - if (flags(flag::is_scanned)) - return; - - auto pfd_verbose = internal::getenv("PFD_VERBOSE"); - auto match_no = std::regex("(|0|no|false)", std::regex_constants::icase); - if (!std::regex_match(pfd_verbose, match_no)) - flags(flag::is_verbose) = true; - -#if _WIN32 - flags(flag::is_vista) = internal::is_vista(); -#elif !__APPLE__ - flags(flag::has_zenity) = check_program("zenity"); - flags(flag::has_matedialog) = check_program("matedialog"); - flags(flag::has_qarma) = check_program("qarma"); - flags(flag::has_kdialog) = check_program("kdialog"); - - // If multiple helpers are available, try to default to the best one - if (flags(flag::has_zenity) && flags(flag::has_kdialog)) - { - auto desktop_name = internal::getenv("XDG_SESSION_DESKTOP"); - if (desktop_name == std::string("gnome")) - flags(flag::has_kdialog) = false; - else if (desktop_name == std::string("KDE")) - flags(flag::has_zenity) = false; - } -#endif - - flags(flag::is_scanned) = true; -} - -inline bool settings::available() -{ -#if _WIN32 - return true; -#elif __APPLE__ - return true; -#elif __EMSCRIPTEN__ - // FIXME: Return true after implementation is complete. - return false; -#else - settings tmp; - return tmp.flags(flag::has_zenity) || - tmp.flags(flag::has_matedialog) || - tmp.flags(flag::has_qarma) || - tmp.flags(flag::has_kdialog); -#endif -} - -inline void settings::verbose(bool value) -{ - settings().flags(flag::is_verbose) = value; -} - -inline void settings::rescan() -{ - settings(/* resync = */ true); -} - -// Check whether a program is present using “which”. -inline bool settings::check_program(std::string const &program) -{ -#if _WIN32 - (void)program; - return false; -#elif __EMSCRIPTEN__ - (void)program; - return false; -#else - int exit_code = -1; - internal::executor async; - async.start_process({"/bin/sh", "-c", "which " + program}); - async.result(&exit_code); - return exit_code == 0; -#endif -} - -inline bool settings::is_osascript() const -{ -#if __APPLE__ - return true; -#else - return false; -#endif -} - -inline bool settings::is_zenity() const -{ - return flags(flag::has_zenity) || - flags(flag::has_matedialog) || - flags(flag::has_qarma); -} - -inline bool settings::is_kdialog() const -{ - return flags(flag::has_kdialog); -} - -inline bool const &settings::flags(flag in_flag) const -{ - static bool flags[size_t(flag::max_flag)]; - return flags[size_t(in_flag)]; -} - -inline bool &settings::flags(flag in_flag) -{ - return const_cast(static_cast(this)->flags(in_flag)); -} - -// path implementation -inline std::string path::home() -{ -#if _WIN32 - // First try the USERPROFILE environment variable - auto user_profile = internal::getenv("USERPROFILE"); - if (user_profile.size() > 0) - return user_profile; - // Otherwise, try GetUserProfileDirectory() - HANDLE token = nullptr; - DWORD len = MAX_PATH; - char buf[MAX_PATH] = { '\0' }; - if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) - { - dll userenv("userenv.dll"); - dll::proc get_user_profile_directory(userenv, "GetUserProfileDirectoryA"); - get_user_profile_directory(token, buf, &len); - CloseHandle(token); - if (*buf) - return buf; - } -#elif __EMSCRIPTEN__ - return "/"; -#else - // First try the HOME environment variable - auto home = internal::getenv("HOME"); - if (home.size() > 0) - return home; - // Otherwise, try getpwuid_r() - size_t len = 4096; -#if defined(_SC_GETPW_R_SIZE_MAX) - auto size_max = sysconf(_SC_GETPW_R_SIZE_MAX); - if (size_max != -1) - len = size_t(size_max); -#endif - std::vector buf(len); - struct passwd pwd, *result; - if (getpwuid_r(getuid(), &pwd, buf.data(), buf.size(), &result) == 0) - return result->pw_dir; -#endif - return "/"; -} - -inline std::string path::separator() -{ -#if _WIN32 - return "\\"; -#else - return "/"; -#endif -} - -// executor implementation - -inline std::string internal::executor::result(int *exit_code /* = nullptr */) -{ - stop(); - if (exit_code) - *exit_code = m_exit_code; - return m_stdout; -} - -inline bool internal::executor::kill() -{ -#if _WIN32 - if (m_future.valid()) - { - // Close all windows that weren’t open when we started the future - auto previous_windows = m_windows; - EnumWindows(&enum_windows_callback, (LPARAM)this); - for (auto hwnd : m_windows) - if (previous_windows.find(hwnd) == previous_windows.end()) - { - SendMessage(hwnd, WM_CLOSE, 0, 0); - // Also send IDNO in case of a Yes/No or Abort/Retry/Ignore messagebox - SendMessage(hwnd, WM_COMMAND, IDNO, 0); - } - } -#elif __EMSCRIPTEN__ || __NX__ - // FIXME: do something - return false; // cannot kill -#else - ::kill(m_pid, SIGKILL); -#endif - stop(); - return true; -} - -#if _WIN32 -inline BOOL CALLBACK internal::executor::enum_windows_callback(HWND hwnd, LPARAM lParam) -{ - auto that = (executor *)lParam; - - DWORD pid; - auto tid = GetWindowThreadProcessId(hwnd, &pid); - if (tid == that->m_tid) - that->m_windows.insert(hwnd); - return TRUE; -} -#endif - -#if _WIN32 -inline void internal::executor::start_func(std::function const &fun) -{ - stop(); - - auto trampoline = [fun, this]() - { - // Save our thread id so that the caller can cancel us - m_tid = GetCurrentThreadId(); - EnumWindows(&enum_windows_callback, (LPARAM)this); - m_cond.notify_all(); - return fun(&m_exit_code); - }; - - std::unique_lock lock(m_mutex); - m_future = std::async(std::launch::async, trampoline); - m_cond.wait(lock); - m_running = true; -} - -#elif __EMSCRIPTEN__ -inline void internal::executor::start(int exit_code) -{ - m_exit_code = exit_code; -} - -#else -inline void internal::executor::start_process(std::vector const &command) -{ - stop(); - m_stdout.clear(); - m_exit_code = -1; - - int in[2], out[2]; - if (pipe(in) != 0 || pipe(out) != 0) - return; - - m_pid = fork(); - if (m_pid < 0) - return; - - close(in[m_pid ? 0 : 1]); - close(out[m_pid ? 1 : 0]); - - if (m_pid == 0) - { - dup2(in[0], STDIN_FILENO); - dup2(out[1], STDOUT_FILENO); - - // Ignore stderr so that it doesn’t pollute the console (e.g. GTK+ errors from zenity) - int fd = open("/dev/null", O_WRONLY); - dup2(fd, STDERR_FILENO); - close(fd); - - std::vector args; - std::transform(command.cbegin(), command.cend(), std::back_inserter(args), - [](std::string const &s) { return const_cast(s.c_str()); }); - args.push_back(nullptr); // null-terminate argv[] - - execvp(args[0], args.data()); - exit(1); - } - - close(in[1]); - m_fd = out[0]; - auto flags = fcntl(m_fd, F_GETFL); - fcntl(m_fd, F_SETFL, flags | O_NONBLOCK); - - m_running = true; -} -#endif - -inline internal::executor::~executor() -{ - stop(); -} - -inline bool internal::executor::ready(int timeout /* = default_wait_timeout */) -{ - if (!m_running) - return true; - -#if _WIN32 - if (m_future.valid()) - { - auto status = m_future.wait_for(std::chrono::milliseconds(timeout)); - if (status != std::future_status::ready) - { - // On Windows, we need to run the message pump. If the async - // thread uses a Windows API dialog, it may be attached to the - // main thread and waiting for messages that only we can dispatch. - MSG msg; - while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - return false; - } - - m_stdout = m_future.get(); - } -#elif __EMSCRIPTEN__ || __NX__ - // FIXME: do something - (void)timeout; -#else - char buf[BUFSIZ]; - ssize_t received = read(m_fd, buf, BUFSIZ); // Flawfinder: ignore - if (received > 0) - { - m_stdout += std::string(buf, received); - return false; - } - - // Reap child process if it is dead. It is possible that the system has already reaped it - // (this happens when the calling application handles or ignores SIG_CHLD) and results in - // waitpid() failing with ECHILD. Otherwise we assume the child is running and we sleep for - // a little while. - int status; - pid_t child = waitpid(m_pid, &status, WNOHANG); - if (child != m_pid && (child >= 0 || errno != ECHILD)) - { - // FIXME: this happens almost always at first iteration - std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); - return false; - } - - close(m_fd); - m_exit_code = WEXITSTATUS(status); -#endif - - m_running = false; - return true; -} - -inline void internal::executor::stop() -{ - // Loop until the user closes the dialog - while (!ready()) - ; -} - -// dll implementation - -#if _WIN32 -inline internal::platform::dll::dll(std::string const &name) - : handle(::LoadLibraryA(name.c_str())) -{} - -inline internal::platform::dll::~dll() -{ - if (handle) - ::FreeLibrary(handle); -} -#endif // _WIN32 - -// ole32_dll implementation - -#if _WIN32 -inline internal::platform::ole32_dll::ole32_dll() - : dll("ole32.dll") -{ - // Use COINIT_MULTITHREADED because COINIT_APARTMENTTHREADED causes crashes. - // See https://github.com/samhocevar/portable-file-dialogs/issues/51 - auto coinit = proc(*this, "CoInitializeEx"); - m_state = coinit(nullptr, COINIT_MULTITHREADED); -} - -inline internal::platform::ole32_dll::~ole32_dll() -{ - if (is_initialized()) - proc(*this, "CoUninitialize")(); -} - -inline bool internal::platform::ole32_dll::is_initialized() -{ - return m_state == S_OK || m_state == S_FALSE; -} -#endif - -// new_style_context implementation - -#if _WIN32 -inline internal::platform::new_style_context::new_style_context() -{ - // Only create one activation context for the whole app lifetime. - static HANDLE hctx = create(); - - if (hctx != INVALID_HANDLE_VALUE) - ActivateActCtx(hctx, &m_cookie); -} - -inline internal::platform::new_style_context::~new_style_context() -{ - DeactivateActCtx(0, m_cookie); -} - -inline HANDLE internal::platform::new_style_context::create() -{ - // This “hack” seems to be necessary for this code to work on windows XP. - // Without it, dialogs do not show and close immediately. GetError() - // returns 0 so I don’t know what causes this. I was not able to reproduce - // this behavior on Windows 7 and 10 but just in case, let it be here for - // those versions too. - // This hack is not required if other dialogs are used (they load comdlg32 - // automatically), only if message boxes are used. - dll comdlg32("comdlg32.dll"); - - // Using approach as shown here: https://stackoverflow.com/a/10444161 - UINT len = ::GetSystemDirectoryA(nullptr, 0); - std::string sys_dir(len, '\0'); - ::GetSystemDirectoryA(&sys_dir[0], len); - - ACTCTXA act_ctx = - { - // Do not set flag ACTCTX_FLAG_SET_PROCESS_DEFAULT, since it causes a - // crash with error “default context is already set”. - sizeof(act_ctx), - ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID, - "shell32.dll", 0, 0, sys_dir.c_str(), (LPCSTR)124, nullptr, 0, - }; - - return ::CreateActCtxA(&act_ctx); -} -#endif // _WIN32 - -// dialog implementation - -inline bool internal::dialog::ready(int timeout /* = default_wait_timeout */) const -{ - return m_async->ready(timeout); -} - -inline bool internal::dialog::kill() const -{ - return m_async->kill(); -} - -inline internal::dialog::dialog() - : m_async(std::make_shared()) -{ -} - -inline std::vector internal::dialog::desktop_helper() const -{ -#if __APPLE__ - return { "osascript" }; -#else - return { flags(flag::has_zenity) ? "zenity" - : flags(flag::has_matedialog) ? "matedialog" - : flags(flag::has_qarma) ? "qarma" - : flags(flag::has_kdialog) ? "kdialog" - : "echo" }; -#endif -} - -inline std::string internal::dialog::buttons_to_name(choice _choice) -{ - switch (_choice) - { - case choice::ok_cancel: return "okcancel"; - case choice::yes_no: return "yesno"; - case choice::yes_no_cancel: return "yesnocancel"; - case choice::retry_cancel: return "retrycancel"; - case choice::abort_retry_ignore: return "abortretryignore"; - /* case choice::ok: */ default: return "ok"; - } -} - -inline std::string internal::dialog::get_icon_name(icon _icon) -{ - switch (_icon) - { - case icon::warning: return "warning"; - case icon::error: return "error"; - case icon::question: return "question"; - // Zenity wants "information" but WinForms wants "info" - /* case icon::info: */ default: -#if _WIN32 - return "info"; -#else - return "information"; -#endif - } -} - -// This is only used for debugging purposes -inline std::ostream& operator <<(std::ostream &s, std::vector const &v) -{ - int not_first = 0; - for (auto &e : v) - s << (not_first++ ? " " : "") << e; - return s; -} - -// Properly quote a string for Powershell: replace ' or " with '' or "" -// FIXME: we should probably get rid of newlines! -// FIXME: the \" sequence seems unsafe, too! -// XXX: this is no longer used but I would like to keep it around just in case -inline std::string internal::dialog::powershell_quote(std::string const &str) const -{ - return "'" + std::regex_replace(str, std::regex("['\"]"), "$&$&") + "'"; -} - -// Properly quote a string for osascript: replace \ or " with \\ or \" -// XXX: this also used to replace ' with \' when popen was used, but it would be -// smarter to do shell_quote(osascript_quote(...)) if this is needed again. -inline std::string internal::dialog::osascript_quote(std::string const &str) const -{ - return "\"" + std::regex_replace(str, std::regex("[\\\\\"]"), "\\$&") + "\""; -} - -// Properly quote a string for the shell: just replace ' with '\'' -// XXX: this is no longer used but I would like to keep it around just in case -inline std::string internal::dialog::shell_quote(std::string const &str) const -{ - return "'" + std::regex_replace(str, std::regex("'"), "'\\''") + "'"; -} - -// file_dialog implementation - -inline internal::file_dialog::file_dialog(type in_type, - std::string const &title, - std::string const &default_path /* = "" */, - std::vector const &filters /* = {} */, - opt options /* = opt::none */) -{ -#if _WIN32 - std::string filter_list; - std::regex whitespace(" *"); - for (size_t i = 0; i + 1 < filters.size(); i += 2) - { - filter_list += filters[i] + '\0'; - filter_list += std::regex_replace(filters[i + 1], whitespace, ";") + '\0'; - } - filter_list += '\0'; - - m_async->start_func([this, in_type, title, default_path, filter_list, - options](int *exit_code) -> std::string - { - (void)exit_code; - m_wtitle = internal::str2wstr(title); - m_wdefault_path = internal::str2wstr(default_path); - auto wfilter_list = internal::str2wstr(filter_list); - - // Initialise COM. This is required for the new folder selection window, - // (see https://github.com/samhocevar/portable-file-dialogs/pull/21) - // and to avoid random crashes with GetOpenFileNameW() (see - // https://github.com/samhocevar/portable-file-dialogs/issues/51) - ole32_dll ole32; - - // Folder selection uses a different method - if (in_type == type::folder) - { -#if PFD_HAS_IFILEDIALOG - if (flags(flag::is_vista)) - { - // On Vista and higher we should be able to use IFileDialog for folder selection - IFileDialog *ifd; - HRESULT hr = dll::proc(ole32, "CoCreateInstance") - (CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&ifd)); - - // In case CoCreateInstance fails (which it should not), try legacy approach - if (SUCCEEDED(hr)) - return select_folder_vista(ifd, options & opt::force_path); - } -#endif - - BROWSEINFOW bi; - memset(&bi, 0, sizeof(bi)); - - bi.lpfn = &bffcallback; - bi.lParam = (LPARAM)this; - - if (flags(flag::is_vista)) - { - if (ole32.is_initialized()) - bi.ulFlags |= BIF_NEWDIALOGSTYLE; - bi.ulFlags |= BIF_EDITBOX; - bi.ulFlags |= BIF_STATUSTEXT; - } - - auto *list = SHBrowseForFolderW(&bi); - std::string ret; - if (list) - { - auto buffer = new wchar_t[MAX_PATH]; - SHGetPathFromIDListW(list, buffer); - dll::proc(ole32, "CoTaskMemFree")(list); - ret = internal::wstr2str(buffer); - delete[] buffer; - } - return ret; - } - - OPENFILENAMEW ofn; - memset(&ofn, 0, sizeof(ofn)); - ofn.lStructSize = sizeof(OPENFILENAMEW); - ofn.hwndOwner = GetActiveWindow(); - - ofn.lpstrFilter = wfilter_list.c_str(); - - auto woutput = std::wstring(MAX_PATH * 256, L'\0'); - ofn.lpstrFile = (LPWSTR)woutput.data(); - ofn.nMaxFile = (DWORD)woutput.size(); - if (!m_wdefault_path.empty()) - { - // If a directory was provided, use it as the initial directory. If - // a valid path was provided, use it as the initial file. Otherwise, - // let the Windows API decide. - auto path_attr = GetFileAttributesW(m_wdefault_path.c_str()); - if (path_attr != INVALID_FILE_ATTRIBUTES && (path_attr & FILE_ATTRIBUTE_DIRECTORY)) - ofn.lpstrInitialDir = m_wdefault_path.c_str(); - else if (m_wdefault_path.size() <= woutput.size()) - //second argument is size of buffer, not length of string - StringCchCopyW(ofn.lpstrFile, MAX_PATH*256+1, m_wdefault_path.c_str()); - else - { - ofn.lpstrFileTitle = (LPWSTR)m_wdefault_path.data(); - ofn.nMaxFileTitle = (DWORD)m_wdefault_path.size(); - } - } - ofn.lpstrTitle = m_wtitle.c_str(); - ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER; - - dll comdlg32("comdlg32.dll"); - - // Apply new visual style (required for windows XP) - new_style_context ctx; - - if (in_type == type::save) - { - if (!(options & opt::force_overwrite)) - ofn.Flags |= OFN_OVERWRITEPROMPT; - - dll::proc get_save_file_name(comdlg32, "GetSaveFileNameW"); - if (get_save_file_name(&ofn) == 0) - return ""; - return internal::wstr2str(woutput.c_str()); - } - else - { - if (options & opt::multiselect) - ofn.Flags |= OFN_ALLOWMULTISELECT; - ofn.Flags |= OFN_PATHMUSTEXIST; - - dll::proc get_open_file_name(comdlg32, "GetOpenFileNameW"); - if (get_open_file_name(&ofn) == 0) - return ""; - } - - std::string prefix; - for (wchar_t const *p = woutput.c_str(); *p; ) - { - auto filename = internal::wstr2str(p); - p += wcslen(p); - // In multiselect mode, we advance p one wchar further and - // check for another filename. If there is one and the - // prefix is empty, it means we just read the prefix. - if ((options & opt::multiselect) && *++p && prefix.empty()) - { - prefix = filename + "/"; - continue; - } - - m_vector_result.push_back(prefix + filename); - } - - return ""; - }); -#elif __EMSCRIPTEN__ - // FIXME: do something - (void)in_type; - (void)title; - (void)default_path; - (void)filters; - (void)options; -#else - auto command = desktop_helper(); - - if (is_osascript()) - { - std::string script = "set ret to choose"; - switch (in_type) - { - case type::save: - script += " file name"; - break; - case type::open: default: - script += " file"; - if (options & opt::multiselect) - script += " with multiple selections allowed"; - break; - case type::folder: - script += " folder"; - break; - } - - if (default_path.size()) - { - if (in_type == type::folder || is_directory(default_path)) - script += " default location "; - else - script += " default name "; - script += osascript_quote(default_path); - } - - script += " with prompt " + osascript_quote(title); - - if (in_type == type::open) - { - // Concatenate all user-provided filter patterns - std::string patterns; - for (size_t i = 0; i < filters.size() / 2; ++i) - patterns += " " + filters[2 * i + 1]; - - // Split the pattern list to check whether "*" is in there; if it - // is, we have to disable filters because there is no mechanism in - // OS X for the user to override the filter. - std::regex sep("\\s+"); - std::string filter_list; - bool has_filter = true; - std::sregex_token_iterator iter(patterns.begin(), patterns.end(), sep, -1); - std::sregex_token_iterator end; - for ( ; iter != end; ++iter) - { - auto pat = iter->str(); - if (pat == "*" || pat == "*.*") - has_filter = false; - else if (internal::starts_with(pat, "*.")) - filter_list += "," + osascript_quote(pat.substr(2, pat.size() - 2)); - } - - if (has_filter && filter_list.size() > 0) - { - // There is a weird AppleScript bug where file extensions of length != 3 are - // ignored, e.g. type{"txt"} works, but type{"json"} does not. Fortunately if - // the whole list starts with a 3-character extension, everything works again. - // We use "///" for such an extension because we are sure it cannot appear in - // an actual filename. - script += " of type {\"///\"" + filter_list + "}"; - } - } - - if (in_type == type::open && (options & opt::multiselect)) - { - script += "\nset s to \"\""; - script += "\nrepeat with i in ret"; - script += "\n set s to s & (POSIX path of i) & \"\\n\""; - script += "\nend repeat"; - script += "\ncopy s to stdout"; - } - else - { - script += "\nPOSIX path of ret"; - } - - command.push_back("-e"); - command.push_back(script); - } - else if (is_zenity()) - { - command.push_back("--file-selection"); - - // If the default path is a directory, make sure it ends with "/" otherwise zenity will - // open the file dialog in the parent directory. - auto filename_arg = "--filename=" + default_path; - if (in_type != type::folder && !ends_with(default_path, "/") && internal::is_directory(default_path)) - filename_arg += "/"; - command.push_back(filename_arg); - - command.push_back("--title"); - command.push_back(title); - command.push_back("--separator=\n"); - - for (size_t i = 0; i < filters.size() / 2; ++i) - { - command.push_back("--file-filter"); - command.push_back(filters[2 * i] + "|" + filters[2 * i + 1]); - } - - if (in_type == type::save) - command.push_back("--save"); - if (in_type == type::folder) - command.push_back("--directory"); - if (!(options & opt::force_overwrite)) - command.push_back("--confirm-overwrite"); - if (options & opt::multiselect) - command.push_back("--multiple"); - } - else if (is_kdialog()) - { - switch (in_type) - { - case type::save: command.push_back("--getsavefilename"); break; - case type::open: command.push_back("--getopenfilename"); break; - case type::folder: command.push_back("--getexistingdirectory"); break; - } - if (options & opt::multiselect) - { - command.push_back("--multiple"); - command.push_back("--separate-output"); - } - - command.push_back(default_path); - - std::string filter; - for (size_t i = 0; i < filters.size() / 2; ++i) - filter += (i == 0 ? "" : " | ") + filters[2 * i] + "(" + filters[2 * i + 1] + ")"; - command.push_back(filter); - - command.push_back("--title"); - command.push_back(title); - } - - if (flags(flag::is_verbose)) - std::cerr << "pfd: " << command << std::endl; - - m_async->start_process(command); -#endif -} - -inline std::string internal::file_dialog::string_result() -{ -#if _WIN32 - return m_async->result(); -#else - auto ret = m_async->result(); - // Strip potential trailing newline (zenity). Also strip trailing slash - // added by osascript for consistency with other backends. - while (!ret.empty() && (ret.back() == '\n' || ret.back() == '/')) - ret.pop_back(); - return ret; -#endif -} - -inline std::vector internal::file_dialog::vector_result() -{ -#if _WIN32 - m_async->result(); - return m_vector_result; -#else - std::vector ret; - auto result = m_async->result(); - for (;;) - { - // Split result along newline characters - auto i = result.find('\n'); - if (i == 0 || i == std::string::npos) - break; - ret.push_back(result.substr(0, i)); - result = result.substr(i + 1, result.size()); - } - return ret; -#endif -} - -#if _WIN32 -// Use a static function to pass as BFFCALLBACK for legacy folder select -inline int CALLBACK internal::file_dialog::bffcallback(HWND hwnd, UINT uMsg, - LPARAM, LPARAM pData) -{ - auto inst = (file_dialog *)pData; - switch (uMsg) - { - case BFFM_INITIALIZED: - SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM)inst->m_wdefault_path.c_str()); - break; - } - return 0; -} - -#if PFD_HAS_IFILEDIALOG -inline std::string internal::file_dialog::select_folder_vista(IFileDialog *ifd, bool force_path) -{ - std::string result; - - IShellItem *folder; - - // Load library at runtime so app doesn't link it at load time (which will fail on windows XP) - dll shell32("shell32.dll"); - dll::proc - create_item(shell32, "SHCreateItemFromParsingName"); - - if (!create_item) - return ""; - - auto hr = create_item(m_wdefault_path.c_str(), - nullptr, - IID_PPV_ARGS(&folder)); - - // Set default folder if found. This only sets the default folder. If - // Windows has any info about the most recently selected folder, it - // will display it instead. Generally, calling SetFolder() to set the - // current directory “is not a good or expected user experience and - // should therefore be avoided”: - // https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfolder - if (SUCCEEDED(hr)) - { - if (force_path) - ifd->SetFolder(folder); - else - ifd->SetDefaultFolder(folder); - folder->Release(); - } - - // Set the dialog title and option to select folders - ifd->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM); - ifd->SetTitle(m_wtitle.c_str()); - - hr = ifd->Show(GetActiveWindow()); - if (SUCCEEDED(hr)) - { - IShellItem* item; - hr = ifd->GetResult(&item); - if (SUCCEEDED(hr)) - { - wchar_t* wname = nullptr; - // This is unlikely to fail because we use FOS_FORCEFILESYSTEM, but try - // to output a debug message just in case. - if (SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &wname))) - { - result = internal::wstr2str(std::wstring(wname)); - dll::proc(ole32_dll(), "CoTaskMemFree")(wname); - } - else - { - if (SUCCEEDED(item->GetDisplayName(SIGDN_NORMALDISPLAY, &wname))) - { - auto name = internal::wstr2str(std::wstring(wname)); - dll::proc(ole32_dll(), "CoTaskMemFree")(wname); - std::cerr << "pfd: failed to get path for " << name << std::endl; - } - else - std::cerr << "pfd: item of unknown type selected" << std::endl; - } - - item->Release(); - } - } - - ifd->Release(); - - return result; -} -#endif -#endif - -// notify implementation - -inline notify::notify(std::string const &title, - std::string const &message, - icon _icon /* = icon::info */) -{ - if (_icon == icon::question) // Not supported by notifications - _icon = icon::info; - -#if _WIN32 - // Use a static shared pointer for notify_icon so that we can delete - // it whenever we need to display a new one, and we can also wait - // until the program has finished running. - struct notify_icon_data : public NOTIFYICONDATAW - { - ~notify_icon_data() { Shell_NotifyIconW(NIM_DELETE, this); } - }; - - static std::shared_ptr nid; - - // Release the previous notification icon, if any, and allocate a new - // one. Note that std::make_shared() does value initialization, so there - // is no need to memset the structure. - nid = nullptr; - nid = std::make_shared(); - - // For XP support - nid->cbSize = NOTIFYICONDATAW_V2_SIZE; - nid->hWnd = nullptr; - nid->uID = 0; - - // Flag Description: - // - NIF_ICON The hIcon member is valid. - // - NIF_MESSAGE The uCallbackMessage member is valid. - // - NIF_TIP The szTip member is valid. - // - NIF_STATE The dwState and dwStateMask members are valid. - // - NIF_INFO Use a balloon ToolTip instead of a standard ToolTip. The szInfo, uTimeout, szInfoTitle, and dwInfoFlags members are valid. - // - NIF_GUID Reserved. - nid->uFlags = NIF_MESSAGE | NIF_ICON | NIF_INFO; - - // Flag Description - // - NIIF_ERROR An error icon. - // - NIIF_INFO An information icon. - // - NIIF_NONE No icon. - // - NIIF_WARNING A warning icon. - // - NIIF_ICON_MASK Version 6.0. Reserved. - // - NIIF_NOSOUND Version 6.0. Do not play the associated sound. Applies only to balloon ToolTips - switch (_icon) - { - case icon::warning: nid->dwInfoFlags = NIIF_WARNING; break; - case icon::error: nid->dwInfoFlags = NIIF_ERROR; break; - /* case icon::info: */ default: nid->dwInfoFlags = NIIF_INFO; break; - } - - ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, LONG_PTR lParam) -> BOOL - { - ((NOTIFYICONDATAW *)lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName); - return false; - }; - - nid->hIcon = ::LoadIcon(nullptr, IDI_APPLICATION); - ::EnumResourceNames(nullptr, RT_GROUP_ICON, icon_enum_callback, (LONG_PTR)nid.get()); - - nid->uTimeout = 5000; - - StringCchCopyW(nid->szInfoTitle, ARRAYSIZE(nid->szInfoTitle), internal::str2wstr(title).c_str()); - StringCchCopyW(nid->szInfo, ARRAYSIZE(nid->szInfo), internal::str2wstr(message).c_str()); - - // Display the new icon - Shell_NotifyIconW(NIM_ADD, nid.get()); -#elif __EMSCRIPTEN__ - // FIXME: do something - (void)title; - (void)message; -#else - auto command = desktop_helper(); - - if (is_osascript()) - { - command.push_back("-e"); - command.push_back("display notification " + osascript_quote(message) + - " with title " + osascript_quote(title)); - } - else if (is_zenity()) - { - command.push_back("--notification"); - command.push_back("--window-icon"); - command.push_back(get_icon_name(_icon)); - command.push_back("--text"); - command.push_back(title + "\n" + message); - } - else if (is_kdialog()) - { - command.push_back("--icon"); - command.push_back(get_icon_name(_icon)); - command.push_back("--title"); - command.push_back(title); - command.push_back("--passivepopup"); - command.push_back(message); - command.push_back("5"); - } - - if (flags(flag::is_verbose)) - std::cerr << "pfd: " << command << std::endl; - - m_async->start_process(command); -#endif -} - -// message implementation - -inline message::message(std::string const &title, - std::string const &text, - choice _choice /* = choice::ok_cancel */, - icon _icon /* = icon::info */) -{ -#if _WIN32 - // Use MB_SYSTEMMODAL rather than MB_TOPMOST to ensure the message window is brought - // to front. See https://github.com/samhocevar/portable-file-dialogs/issues/52 - UINT style = MB_SYSTEMMODAL; - switch (_icon) - { - case icon::warning: style |= MB_ICONWARNING; break; - case icon::error: style |= MB_ICONERROR; break; - case icon::question: style |= MB_ICONQUESTION; break; - /* case icon::info: */ default: style |= MB_ICONINFORMATION; break; - } - - switch (_choice) - { - case choice::ok_cancel: style |= MB_OKCANCEL; break; - case choice::yes_no: style |= MB_YESNO; break; - case choice::yes_no_cancel: style |= MB_YESNOCANCEL; break; - case choice::retry_cancel: style |= MB_RETRYCANCEL; break; - case choice::abort_retry_ignore: style |= MB_ABORTRETRYIGNORE; break; - /* case choice::ok: */ default: style |= MB_OK; break; - } - - m_mappings[IDCANCEL] = button::cancel; - m_mappings[IDOK] = button::ok; - m_mappings[IDYES] = button::yes; - m_mappings[IDNO] = button::no; - m_mappings[IDABORT] = button::abort; - m_mappings[IDRETRY] = button::retry; - m_mappings[IDIGNORE] = button::ignore; - - m_async->start_func([text, title, style](int* exit_code) -> std::string - { - auto wtext = internal::str2wstr(text); - auto wtitle = internal::str2wstr(title); - // Apply new visual style (required for all Windows versions) - new_style_context ctx; - *exit_code = MessageBoxW(GetActiveWindow(), wtext.c_str(), wtitle.c_str(), style); - return ""; - }); - -#elif __EMSCRIPTEN__ - std::string full_message; - switch (_icon) - { - case icon::warning: full_message = "⚠️"; break; - case icon::error: full_message = "⛔"; break; - case icon::question: full_message = "❓"; break; - /* case icon::info: */ default: full_message = "ℹ"; break; - } - - full_message += ' ' + title + "\n\n" + text; - - // This does not really start an async task; it just passes the - // EM_ASM_INT return value to a fake start() function. - m_async->start(EM_ASM_INT( - { - if ($1) - return window.confirm(UTF8ToString($0)) ? 0 : -1; - alert(UTF8ToString($0)); - return 0; - }, full_message.c_str(), _choice == choice::ok_cancel)); -#else - auto command = desktop_helper(); - - if (is_osascript()) - { - std::string script = "display dialog " + osascript_quote(text) + - " with title " + osascript_quote(title); - auto if_cancel = button::cancel; - switch (_choice) - { - case choice::ok_cancel: - script += "buttons {\"OK\", \"Cancel\"}" - " default button \"OK\"" - " cancel button \"Cancel\""; - break; - case choice::yes_no: - script += "buttons {\"Yes\", \"No\"}" - " default button \"Yes\"" - " cancel button \"No\""; - if_cancel = button::no; - break; - case choice::yes_no_cancel: - script += "buttons {\"Yes\", \"No\", \"Cancel\"}" - " default button \"Yes\"" - " cancel button \"Cancel\""; - break; - case choice::retry_cancel: - script += "buttons {\"Retry\", \"Cancel\"}" - " default button \"Retry\"" - " cancel button \"Cancel\""; - break; - case choice::abort_retry_ignore: - script += "buttons {\"Abort\", \"Retry\", \"Ignore\"}" - " default button \"Abort\"" - " cancel button \"Retry\""; - if_cancel = button::retry; - break; - case choice::ok: default: - script += "buttons {\"OK\"}" - " default button \"OK\"" - " cancel button \"OK\""; - if_cancel = button::ok; - break; - } - m_mappings[1] = if_cancel; - m_mappings[256] = if_cancel; // XXX: I think this was never correct - script += " with icon "; - switch (_icon) - { - #define PFD_OSX_ICON(n) "alias ((path to library folder from system domain) as text " \ - "& \"CoreServices:CoreTypes.bundle:Contents:Resources:" n ".icns\")" - case icon::info: default: script += PFD_OSX_ICON("ToolBarInfo"); break; - case icon::warning: script += "caution"; break; - case icon::error: script += "stop"; break; - case icon::question: script += PFD_OSX_ICON("GenericQuestionMarkIcon"); break; - #undef PFD_OSX_ICON - } - - command.push_back("-e"); - command.push_back(script); - } - else if (is_zenity()) - { - switch (_choice) - { - case choice::ok_cancel: - command.insert(command.end(), { "--question", "--cancel-label=Cancel", "--ok-label=OK" }); break; - case choice::yes_no: - // Do not use standard --question because it causes “No” to return -1, - // which is inconsistent with the “Yes/No/Cancel” mode below. - command.insert(command.end(), { "--question", "--switch", "--extra-button=No", "--extra-button=Yes" }); break; - case choice::yes_no_cancel: - command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=No", "--extra-button=Yes" }); break; - case choice::retry_cancel: - command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=Retry" }); break; - case choice::abort_retry_ignore: - command.insert(command.end(), { "--question", "--switch", "--extra-button=Ignore", "--extra-button=Abort", "--extra-button=Retry" }); break; - case choice::ok: - default: - switch (_icon) - { - case icon::error: command.push_back("--error"); break; - case icon::warning: command.push_back("--warning"); break; - default: command.push_back("--info"); break; - } - } - - command.insert(command.end(), { "--title", title, - "--width=300", "--height=0", // sensible defaults - "--no-markup", // do not interpret text as Pango markup - "--text", text, - "--icon-name=dialog-" + get_icon_name(_icon) }); - } - else if (is_kdialog()) - { - if (_choice == choice::ok) - { - switch (_icon) - { - case icon::error: command.push_back("--error"); break; - case icon::warning: command.push_back("--sorry"); break; - default: command.push_back("--msgbox"); break; - } - } - else - { - std::string flag = "--"; - if (_icon == icon::warning || _icon == icon::error) - flag += "warning"; - flag += "yesno"; - if (_choice == choice::yes_no_cancel) - flag += "cancel"; - command.push_back(flag); - if (_choice == choice::yes_no || _choice == choice::yes_no_cancel) - { - m_mappings[0] = button::yes; - m_mappings[256] = button::no; - } - } - - command.push_back(text); - command.push_back("--title"); - command.push_back(title); - - // Must be after the above part - if (_choice == choice::ok_cancel) - command.insert(command.end(), { "--yes-label", "OK", "--no-label", "Cancel" }); - } - - if (flags(flag::is_verbose)) - std::cerr << "pfd: " << command << std::endl; - - m_async->start_process(command); -#endif -} - -inline button message::result() -{ - int exit_code; - auto ret = m_async->result(&exit_code); - // osascript will say "button returned:Cancel\n" - // and others will just say "Cancel\n" - if (internal::ends_with(ret, "Cancel\n")) - return button::cancel; - if (internal::ends_with(ret, "OK\n")) - return button::ok; - if (internal::ends_with(ret, "Yes\n")) - return button::yes; - if (internal::ends_with(ret, "No\n")) - return button::no; - if (internal::ends_with(ret, "Abort\n")) - return button::abort; - if (internal::ends_with(ret, "Retry\n")) - return button::retry; - if (internal::ends_with(ret, "Ignore\n")) - return button::ignore; - if (m_mappings.count(exit_code) != 0) - return m_mappings[exit_code]; - return exit_code == 0 ? button::ok : button::cancel; -} - -// open_file implementation - -inline open_file::open_file(std::string const &title, - std::string const &default_path /* = "" */, - std::vector const &filters /* = { "All Files", "*" } */, - opt options /* = opt::none */) - : file_dialog(type::open, title, default_path, filters, options) -{ -} - -inline open_file::open_file(std::string const &title, - std::string const &default_path, - std::vector const &filters, - bool allow_multiselect) - : open_file(title, default_path, filters, - (allow_multiselect ? opt::multiselect : opt::none)) -{ -} - -inline std::vector open_file::result() -{ - return vector_result(); -} - -// save_file implementation - -inline save_file::save_file(std::string const &title, - std::string const &default_path /* = "" */, - std::vector const &filters /* = { "All Files", "*" } */, - opt options /* = opt::none */) - : file_dialog(type::save, title, default_path, filters, options) -{ -} - -inline save_file::save_file(std::string const &title, - std::string const &default_path, - std::vector const &filters, - bool confirm_overwrite) - : save_file(title, default_path, filters, - (confirm_overwrite ? opt::none : opt::force_overwrite)) -{ -} - -inline std::string save_file::result() -{ - return string_result(); -} - -// select_folder implementation - -inline select_folder::select_folder(std::string const &title, - std::string const &default_path /* = "" */, - opt options /* = opt::none */) - : file_dialog(type::folder, title, default_path, {}, options) -{ -} - -inline std::string select_folder::result() -{ - return string_result(); -} - -#endif // PFD_SKIP_IMPLEMENTATION - -} // namespace pfd diff --git a/Include/Pipe/Extern/sparse_hash.h b/Include/Pipe/Extern/sparse_hash.h index 90a7fc60..23871daf 100644 --- a/Include/Pipe/Extern/sparse_hash.h +++ b/Include/Pipe/Extern/sparse_hash.h @@ -24,6 +24,8 @@ #ifndef TSL_SPARSE_HASH_H #define TSL_SPARSE_HASH_H +#include "Pipe/Extern/sparse_growth_policy.h" + #include #include #include @@ -38,2199 +40,2493 @@ #include #include -#include "Pipe/Extern/sparse_growth_policy.h" #ifdef __INTEL_COMPILER -#include // For _popcnt32 and _popcnt64 + #include // For _popcnt32 and _popcnt64 #endif #ifdef _MSC_VER -#include // For __cpuid, __popcnt and __popcnt64 + #include // For __cpuid, __popcnt and __popcnt64 #endif #ifdef TSL_DEBUG -#define tsl_sh_assert(expr) assert(expr) + #define tsl_sh_assert(expr) assert(expr) #else -#define tsl_sh_assert(expr) (static_cast(0)) + #define tsl_sh_assert(expr) (static_cast(0)) #endif -namespace tsl { - -namespace sh { -enum class probing { linear, quadratic }; - -enum class exception_safety { basic, strong }; - -enum class sparsity { high, medium, low }; -} // namespace sh - -namespace detail_popcount { -/** - * Define the popcount(ll) methods and pick-up the best depending on the - * compiler. - */ - -// From Wikipedia: https://en.wikipedia.org/wiki/Hamming_weight -inline int fallback_popcountll(unsigned long long int x) { - static_assert( - sizeof(unsigned long long int) == sizeof(std::uint64_t), - "sizeof(unsigned long long int) must be equal to sizeof(std::uint64_t). " - "Open a feature request if you need support for a platform where it " - "isn't the case."); - - const std::uint64_t m1 = 0x5555555555555555ull; - const std::uint64_t m2 = 0x3333333333333333ull; - const std::uint64_t m4 = 0x0f0f0f0f0f0f0f0full; - const std::uint64_t h01 = 0x0101010101010101ull; - - x -= (x >> 1ull) & m1; - x = (x & m2) + ((x >> 2ull) & m2); - x = (x + (x >> 4ull)) & m4; - return static_cast((x * h01) >> (64ull - 8ull)); -} - -inline int fallback_popcount(unsigned int x) { - static_assert(sizeof(unsigned int) == sizeof(std::uint32_t) || - sizeof(unsigned int) == sizeof(std::uint64_t), - "sizeof(unsigned int) must be equal to sizeof(std::uint32_t) " - "or sizeof(std::uint64_t). " - "Open a feature request if you need support for a platform " - "where it isn't the case."); - - if (sizeof(unsigned int) == sizeof(std::uint32_t)) { - const std::uint32_t m1 = 0x55555555; - const std::uint32_t m2 = 0x33333333; - const std::uint32_t m4 = 0x0f0f0f0f; - const std::uint32_t h01 = 0x01010101; - - x -= (x >> 1) & m1; - x = (x & m2) + ((x >> 2) & m2); - x = (x + (x >> 4)) & m4; - return static_cast((x * h01) >> (32 - 8)); - } else { - return fallback_popcountll(x); - } -} +namespace tsl +{ + + namespace sh + { + enum class probing + { + linear, + quadratic + }; + + enum class exception_safety + { + basic, + strong + }; + + enum class sparsity + { + high, + medium, + low + }; + } // namespace sh + + namespace detail_popcount + { + /** + * Define the popcount(ll) methods and pick-up the best depending on the + * compiler. + */ + + // From Wikipedia: https://en.wikipedia.org/wiki/Hamming_weight + inline int fallback_popcountll(unsigned long long int x) + { + static_assert(sizeof(unsigned long long int) == sizeof(std::uint64_t), + "sizeof(unsigned long long int) must be equal to sizeof(std::uint64_t). " + "Open a feature request if you need support for a platform where it " + "isn't the case."); + + const std::uint64_t m1 = 0x5555555555555555ull; + const std::uint64_t m2 = 0x3333333333333333ull; + const std::uint64_t m4 = 0x0f0f0f0f0f0f0f0full; + const std::uint64_t h01 = 0x0101010101010101ull; + + x -= (x >> 1ull) & m1; + x = (x & m2) + ((x >> 2ull) & m2); + x = (x + (x >> 4ull)) & m4; + return static_cast((x * h01) >> (64ull - 8ull)); + } + + inline int fallback_popcount(unsigned int x) + { + static_assert(sizeof(unsigned int) == sizeof(std::uint32_t) + || sizeof(unsigned int) == sizeof(std::uint64_t), + "sizeof(unsigned int) must be equal to sizeof(std::uint32_t) " + "or sizeof(std::uint64_t). " + "Open a feature request if you need support for a platform " + "where it isn't the case."); + + if (sizeof(unsigned int) == sizeof(std::uint32_t)) + { + const std::uint32_t m1 = 0x55555555; + const std::uint32_t m2 = 0x33333333; + const std::uint32_t m4 = 0x0f0f0f0f; + const std::uint32_t h01 = 0x01010101; + + x -= (x >> 1) & m1; + x = (x & m2) + ((x >> 2) & m2); + x = (x + (x >> 4)) & m4; + return static_cast((x * h01) >> (32 - 8)); + } + else + { + return fallback_popcountll(x); + } + } #if defined(__clang__) || defined(__GNUC__) -inline int popcountll(unsigned long long int value) { - return __builtin_popcountll(value); -} + inline int popcountll(unsigned long long int value) + { + return __builtin_popcountll(value); + } -inline int popcount(unsigned int value) { return __builtin_popcount(value); } + inline int popcount(unsigned int value) + { + return __builtin_popcount(value); + } #elif defined(_MSC_VER) -/** - * We need to check for popcount support at runtime on Windows with __cpuid - * See https://msdn.microsoft.com/en-us/library/bb385231.aspx - */ -inline bool has_popcount_support() { - int cpu_infos[4]; - __cpuid(cpu_infos, 1); - return (cpu_infos[2] & (1 << 23)) != 0; -} - -inline int popcountll(unsigned long long int value) { -#ifdef _WIN64 - static_assert( - sizeof(unsigned long long int) == sizeof(std::int64_t), - "sizeof(unsigned long long int) must be equal to sizeof(std::int64_t). "); - - static const bool has_popcount = has_popcount_support(); - return has_popcount - ? static_cast(__popcnt64(static_cast(value))) - : fallback_popcountll(value); -#else - return fallback_popcountll(value); -#endif -} - -inline int popcount(unsigned int value) { - static_assert(sizeof(unsigned int) == sizeof(std::int32_t), - "sizeof(unsigned int) must be equal to sizeof(std::int32_t). "); - - static const bool has_popcount = has_popcount_support(); - return has_popcount - ? static_cast(__popcnt(static_cast(value))) - : fallback_popcount(value); -} + /** + * We need to check for popcount support at runtime on Windows with __cpuid + * See https://msdn.microsoft.com/en-us/library/bb385231.aspx + */ + inline bool has_popcount_support() + { + int cpu_infos[4]; + __cpuid(cpu_infos, 1); + return (cpu_infos[2] & (1 << 23)) != 0; + } + + inline int popcountll(unsigned long long int value) + { + #ifdef _WIN64 + static_assert(sizeof(unsigned long long int) == sizeof(std::int64_t), + "sizeof(unsigned long long int) must be equal to sizeof(std::int64_t). "); + + static const bool has_popcount = has_popcount_support(); + return has_popcount ? static_cast(__popcnt64(static_cast(value))) + : fallback_popcountll(value); + #else + return fallback_popcountll(value); + #endif + } + + inline int popcount(unsigned int value) + { + static_assert(sizeof(unsigned int) == sizeof(std::int32_t), + "sizeof(unsigned int) must be equal to sizeof(std::int32_t). "); + + static const bool has_popcount = has_popcount_support(); + return has_popcount ? static_cast(__popcnt(static_cast(value))) + : fallback_popcount(value); + } #elif defined(__INTEL_COMPILER) -inline int popcountll(unsigned long long int value) { - static_assert(sizeof(unsigned long long int) == sizeof(__int64), ""); - return _popcnt64(static_cast<__int64>(value)); -} + inline int popcountll(unsigned long long int value) + { + static_assert(sizeof(unsigned long long int) == sizeof(__int64), ""); + return _popcnt64(static_cast<__int64>(value)); + } -inline int popcount(unsigned int value) { - return _popcnt32(static_cast(value)); -} + inline int popcount(unsigned int value) + { + return _popcnt32(static_cast(value)); + } #else -inline int popcountll(unsigned long long int x) { - return fallback_popcountll(x); -} + inline int popcountll(unsigned long long int x) + { + return fallback_popcountll(x); + } -inline int popcount(unsigned int x) { return fallback_popcount(x); } + inline int popcount(unsigned int x) + { + return fallback_popcount(x); + } #endif -} // namespace detail_popcount - -namespace detail_sparse_hash { - -template -struct make_void { - using type = void; -}; - -template -struct has_is_transparent : std::false_type {}; - -template -struct has_is_transparent::type> - : std::true_type {}; - -template -struct is_power_of_two_policy : std::false_type {}; - -template -struct is_power_of_two_policy> - : std::true_type {}; - -inline constexpr bool is_power_of_two(std::size_t value) { - return value != 0 && (value & (value - 1)) == 0; -} - -inline std::size_t round_up_to_power_of_two(std::size_t value) { - if (is_power_of_two(value)) { - return value; - } - - if (value == 0) { - return 1; - } - - --value; - for (std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { - value |= value >> i; - } - - return value + 1; -} - -template -static T numeric_cast(U value, - const char *error_message = "numeric_cast() failed.") { - T ret = static_cast(value); - if (static_cast(ret) != value) { - TSL_SH_THROW_OR_ABORT(std::runtime_error, error_message); - } - - const bool is_same_signedness = - (std::is_unsigned::value && std::is_unsigned::value) || - (std::is_signed::value && std::is_signed::value); - if (!is_same_signedness && (ret < T{}) != (value < U{})) { - TSL_SH_THROW_OR_ABORT(std::runtime_error, error_message); - } - - return ret; -} - -/** - * Fixed size type used to represent size_type values on serialization. Need to - * be big enough to represent a std::size_t on 32 and 64 bits platforms, and - * must be the same size on both platforms. - */ -using slz_size_type = std::uint64_t; -static_assert(std::numeric_limits::max() >= - std::numeric_limits::max(), - "slz_size_type must be >= std::size_t"); - -template -static T deserialize_value(Deserializer &deserializer) { - // MSVC < 2017 is not conformant, circumvent the problem by removing the - // template keyword + } // namespace detail_popcount + + namespace detail_sparse_hash + { + + template + struct make_void + { + using type = void; + }; + + template + struct has_is_transparent : std::false_type + {}; + + template + struct has_is_transparent::type> + : std::true_type + {}; + + template + struct is_power_of_two_policy : std::false_type + {}; + + template + struct is_power_of_two_policy> + : std::true_type + {}; + + inline constexpr bool is_power_of_two(std::size_t value) + { + return value != 0 && (value & (value - 1)) == 0; + } + + inline std::size_t round_up_to_power_of_two(std::size_t value) + { + if (is_power_of_two(value)) + { + return value; + } + + if (value == 0) + { + return 1; + } + + --value; + for (std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) + { + value |= value >> i; + } + + return value + 1; + } + + template + static T numeric_cast(U value, const char* error_message = "numeric_cast() failed.") + { + T ret = static_cast(value); + if (static_cast(ret) != value) + { + TSL_SH_THROW_OR_ABORT(std::runtime_error, error_message); + } + + const bool is_same_signedness = + (std::is_unsigned::value && std::is_unsigned::value) + || (std::is_signed::value && std::is_signed::value); + if (!is_same_signedness && (ret < T{}) != (value < U{})) + { + TSL_SH_THROW_OR_ABORT(std::runtime_error, error_message); + } + + return ret; + } + + /** + * Fixed size type used to represent size_type values on serialization. Need to + * be big enough to represent a std::size_t on 32 and 64 bits platforms, and + * must be the same size on both platforms. + */ + using slz_size_type = std::uint64_t; + static_assert( + std::numeric_limits::max() >= std::numeric_limits::max(), + "slz_size_type must be >= std::size_t"); + + template + static T deserialize_value(Deserializer& deserializer) + { + // MSVC < 2017 is not conformant, circumvent the problem by removing the + // template keyword #if defined(_MSC_VER) && _MSC_VER < 1910 - return deserializer.Deserializer::operator()(); + return deserializer.Deserializer::operator()(); #else - return deserializer.Deserializer::template operator()(); + return deserializer.Deserializer::template operator()(); #endif -} - -/** - * WARNING: the sparse_array class doesn't free the ressources allocated through - * the allocator passed in parameter in each method. You have to manually call - * `clear(Allocator&)` when you don't need a sparse_array object anymore. - * - * The reason is that the sparse_array doesn't store the allocator to avoid - * wasting space in each sparse_array when the allocator has a size > 0. It only - * allocates/deallocates objects with the allocator that is passed in parameter. - * - * - * - * Index denotes a value between [0, BITMAP_NB_BITS), it is an index similar to - * std::vector. Offset denotes the real position in `m_values` corresponding to - * an index. - * - * We are using raw pointers instead of std::vector to avoid loosing - * 2*sizeof(size_t) bytes to store the capacity and size of the vector in each - * sparse_array. We know we can only store up to BITMAP_NB_BITS elements in the - * array, we don't need such big types. - * - * - * T must be nothrow move constructible and/or copy constructible. - * Behaviour is undefined if the destructor of T throws an exception. - * - * See https://smerity.com/articles/2015/google_sparsehash.html for details on - * the idea behinds the implementation. - * - * TODO Check to use std::realloc and std::memmove when possible - */ -template -class sparse_array { - public: - using value_type = T; - using size_type = std::uint_least8_t; - using allocator_type = Allocator; - using iterator = value_type *; - using const_iterator = const value_type *; - - private: - static const size_type CAPACITY_GROWTH_STEP = - (Sparsity == tsl::sh::sparsity::high) ? 2 - : (Sparsity == tsl::sh::sparsity::medium) - ? 4 - : 8; // (Sparsity == tsl::sh::sparsity::low) - - /** - * Bitmap size configuration. - * Use 32 bits for the bitmap on 32-bits or less environnement as popcount on - * 64 bits numbers is slow on these environnement. Use 64 bits bitmap - * otherwise. - */ + } + + /** + * WARNING: the sparse_array class doesn't free the ressources allocated through + * the allocator passed in parameter in each method. You have to manually call + * `clear(Allocator&)` when you don't need a sparse_array object anymore. + * + * The reason is that the sparse_array doesn't store the allocator to avoid + * wasting space in each sparse_array when the allocator has a size > 0. It only + * allocates/deallocates objects with the allocator that is passed in parameter. + * + * + * + * Index denotes a value between [0, BITMAP_NB_BITS), it is an index similar to + * std::vector. Offset denotes the real position in `m_values` corresponding to + * an index. + * + * We are using raw pointers instead of std::vector to avoid loosing + * 2*sizeof(size_t) bytes to store the capacity and size of the vector in each + * sparse_array. We know we can only store up to BITMAP_NB_BITS elements in the + * array, we don't need such big types. + * + * + * T must be nothrow move constructible and/or copy constructible. + * Behaviour is undefined if the destructor of T throws an exception. + * + * See https://smerity.com/articles/2015/google_sparsehash.html for details on + * the idea behinds the implementation. + * + * TODO Check to use std::realloc and std::memmove when possible + */ + template + class sparse_array + { + public: + using value_type = T; + using size_type = std::uint_least8_t; + using allocator_type = Allocator; + using iterator = value_type*; + using const_iterator = const value_type*; + + private: + static const size_type CAPACITY_GROWTH_STEP = + (Sparsity == tsl::sh::sparsity::high) ? 2 + : (Sparsity == tsl::sh::sparsity::medium) + ? 4 + : 8; // (Sparsity == tsl::sh::sparsity::low) + + /** + * Bitmap size configuration. + * Use 32 bits for the bitmap on 32-bits or less environnement as popcount on + * 64 bits numbers is slow on these environnement. Use 64 bits bitmap + * otherwise. + */ #if SIZE_MAX <= UINT32_MAX - using bitmap_type = std::uint_least32_t; - static const std::size_t BITMAP_NB_BITS = 32; - static const std::size_t BUCKET_SHIFT = 5; + using bitmap_type = std::uint_least32_t; + static const std::size_t BITMAP_NB_BITS = 32; + static const std::size_t BUCKET_SHIFT = 5; #else - using bitmap_type = std::uint_least64_t; - static const std::size_t BITMAP_NB_BITS = 64; - static const std::size_t BUCKET_SHIFT = 6; + using bitmap_type = std::uint_least64_t; + static const std::size_t BITMAP_NB_BITS = 64; + static const std::size_t BUCKET_SHIFT = 6; #endif - static const std::size_t BUCKET_MASK = BITMAP_NB_BITS - 1; - - static_assert(is_power_of_two(BITMAP_NB_BITS), - "BITMAP_NB_BITS must be a power of two."); - static_assert(std::numeric_limits::digits >= BITMAP_NB_BITS, - "bitmap_type must be able to hold at least BITMAP_NB_BITS."); - static_assert((std::size_t(1) << BUCKET_SHIFT) == BITMAP_NB_BITS, - "(1 << BUCKET_SHIFT) must be equal to BITMAP_NB_BITS."); - static_assert(std::numeric_limits::max() >= BITMAP_NB_BITS, - "size_type must be big enough to hold BITMAP_NB_BITS."); - static_assert(std::is_unsigned::value, - "bitmap_type must be unsigned."); - static_assert((std::numeric_limits::max() & BUCKET_MASK) == - BITMAP_NB_BITS - 1, - ""); - - public: - /** - * Map an ibucket [0, bucket_count) in the hash table to a sparse_ibucket - * (a sparse_array holds multiple buckets, so there is less sparse_array than - * bucket_count). - * - * The bucket ibucket is in - * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] - * instead of something like m_buckets[ibucket] in a classical hash table. - */ - static std::size_t sparse_ibucket(std::size_t ibucket) { - return ibucket >> BUCKET_SHIFT; - } - - /** - * Map an ibucket [0, bucket_count) in the hash table to an index in the - * sparse_array which corresponds to the bucket. - * - * The bucket ibucket is in - * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] - * instead of something like m_buckets[ibucket] in a classical hash table. - */ - static typename sparse_array::size_type index_in_sparse_bucket( - std::size_t ibucket) { - return static_cast( - ibucket & sparse_array::BUCKET_MASK); - } - - static std::size_t nb_sparse_buckets(std::size_t bucket_count) noexcept { - if (bucket_count == 0) { - return 0; - } - - return std::max( - 1, sparse_ibucket(tsl::detail_sparse_hash::round_up_to_power_of_two( - bucket_count))); - } - - public: - sparse_array() noexcept - : m_values(nullptr), - m_bitmap_vals(0), - m_bitmap_deleted_vals(0), - m_nb_elements(0), - m_capacity(0), - m_last_array(false) {} - - explicit sparse_array(bool last_bucket) noexcept - : m_values(nullptr), - m_bitmap_vals(0), - m_bitmap_deleted_vals(0), - m_nb_elements(0), - m_capacity(0), - m_last_array(last_bucket) {} - - sparse_array(size_type capacity, Allocator &alloc) - : m_values(nullptr), - m_bitmap_vals(0), - m_bitmap_deleted_vals(0), - m_nb_elements(0), - m_capacity(capacity), - m_last_array(false) { - if (m_capacity > 0) { - m_values = alloc.allocate(m_capacity); - tsl_sh_assert(m_values != - nullptr); // allocate should throw if there is a failure - } - } - - sparse_array(const sparse_array &other, Allocator &alloc) - : m_values(nullptr), - m_bitmap_vals(other.m_bitmap_vals), - m_bitmap_deleted_vals(other.m_bitmap_deleted_vals), - m_nb_elements(0), - m_capacity(other.m_capacity), - m_last_array(other.m_last_array) { - tsl_sh_assert(other.m_capacity >= other.m_nb_elements); - if (m_capacity == 0) { - return; - } - - m_values = alloc.allocate(m_capacity); - tsl_sh_assert(m_values != - nullptr); // allocate should throw if there is a failure - TSL_SH_TRY { - for (size_type i = 0; i < other.m_nb_elements; i++) { - construct_value(alloc, m_values + i, other.m_values[i]); - m_nb_elements++; - } - } - TSL_SH_CATCH(...) { - clear(alloc); - TSL_SH_RETRHOW; - } - } - - sparse_array(sparse_array &&other) noexcept - : m_values(other.m_values), - m_bitmap_vals(other.m_bitmap_vals), - m_bitmap_deleted_vals(other.m_bitmap_deleted_vals), - m_nb_elements(other.m_nb_elements), - m_capacity(other.m_capacity), - m_last_array(other.m_last_array) { - other.m_values = nullptr; - other.m_bitmap_vals = 0; - other.m_bitmap_deleted_vals = 0; - other.m_nb_elements = 0; - other.m_capacity = 0; - } - - sparse_array(sparse_array &&other, Allocator &alloc) - : m_values(nullptr), - m_bitmap_vals(other.m_bitmap_vals), - m_bitmap_deleted_vals(other.m_bitmap_deleted_vals), - m_nb_elements(0), - m_capacity(other.m_capacity), - m_last_array(other.m_last_array) { - tsl_sh_assert(other.m_capacity >= other.m_nb_elements); - if (m_capacity == 0) { - return; - } - - m_values = alloc.allocate(m_capacity); - tsl_sh_assert(m_values != - nullptr); // allocate should throw if there is a failure - TSL_SH_TRY { - for (size_type i = 0; i < other.m_nb_elements; i++) { - construct_value(alloc, m_values + i, std::move(other.m_values[i])); - m_nb_elements++; - } - } - TSL_SH_CATCH(...) { - clear(alloc); - TSL_SH_RETRHOW; - } - } - - sparse_array &operator=(const sparse_array &) = delete; - sparse_array &operator=(sparse_array &&) = delete; - - ~sparse_array() noexcept { - // The code that manages the sparse_array must have called clear before - // destruction. See documentation of sparse_array for more details. - tsl_sh_assert(m_capacity == 0 && m_nb_elements == 0 && m_values == nullptr); - } - - iterator begin() noexcept { return m_values; } - iterator end() noexcept { return m_values + m_nb_elements; } - const_iterator begin() const noexcept { return cbegin(); } - const_iterator end() const noexcept { return cend(); } - const_iterator cbegin() const noexcept { return m_values; } - const_iterator cend() const noexcept { return m_values + m_nb_elements; } - - bool empty() const noexcept { return m_nb_elements == 0; } - - size_type size() const noexcept { return m_nb_elements; } - - void clear(allocator_type &alloc) noexcept { - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); - - m_values = nullptr; - m_bitmap_vals = 0; - m_bitmap_deleted_vals = 0; - m_nb_elements = 0; - m_capacity = 0; - } - - bool last() const noexcept { return m_last_array; } - - void set_as_last() noexcept { m_last_array = true; } - - bool has_value(size_type index) const noexcept { - tsl_sh_assert(index < BITMAP_NB_BITS); - return (m_bitmap_vals & (bitmap_type(1) << index)) != 0; - } - - bool has_deleted_value(size_type index) const noexcept { - tsl_sh_assert(index < BITMAP_NB_BITS); - return (m_bitmap_deleted_vals & (bitmap_type(1) << index)) != 0; - } - - iterator value(size_type index) noexcept { - tsl_sh_assert(has_value(index)); - return m_values + index_to_offset(index); - } - - const_iterator value(size_type index) const noexcept { - tsl_sh_assert(has_value(index)); - return m_values + index_to_offset(index); - } - - /** - * Return iterator to set value. - */ - template - iterator set(allocator_type &alloc, size_type index, Args &&...value_args) { - tsl_sh_assert(!has_value(index)); - - const size_type offset = index_to_offset(index); - insert_at_offset(alloc, offset, std::forward(value_args)...); - - m_bitmap_vals = (m_bitmap_vals | (bitmap_type(1) << index)); - m_bitmap_deleted_vals = - (m_bitmap_deleted_vals & ~(bitmap_type(1) << index)); - - m_nb_elements++; - - tsl_sh_assert(has_value(index)); - tsl_sh_assert(!has_deleted_value(index)); - - return m_values + offset; - } - - iterator erase(allocator_type &alloc, iterator position) { - const size_type offset = - static_cast(std::distance(begin(), position)); - return erase(alloc, position, offset_to_index(offset)); - } - - // Return the next value or end if no next value - iterator erase(allocator_type &alloc, iterator position, size_type index) { - tsl_sh_assert(has_value(index)); - tsl_sh_assert(!has_deleted_value(index)); - - const size_type offset = - static_cast(std::distance(begin(), position)); - erase_at_offset(alloc, offset); - - m_bitmap_vals = (m_bitmap_vals & ~(bitmap_type(1) << index)); - m_bitmap_deleted_vals = (m_bitmap_deleted_vals | (bitmap_type(1) << index)); - - m_nb_elements--; - - tsl_sh_assert(!has_value(index)); - tsl_sh_assert(has_deleted_value(index)); - - return m_values + offset; - } - - void swap(sparse_array &other) { - using std::swap; - - swap(m_values, other.m_values); - swap(m_bitmap_vals, other.m_bitmap_vals); - swap(m_bitmap_deleted_vals, other.m_bitmap_deleted_vals); - swap(m_nb_elements, other.m_nb_elements); - swap(m_capacity, other.m_capacity); - swap(m_last_array, other.m_last_array); - } - - static iterator mutable_iterator(const_iterator pos) { - return const_cast(pos); - } - - template - void serialize(Serializer &serializer) const { - const slz_size_type sparse_bucket_size = m_nb_elements; - serializer(sparse_bucket_size); - - const slz_size_type bitmap_vals = m_bitmap_vals; - serializer(bitmap_vals); - - const slz_size_type bitmap_deleted_vals = m_bitmap_deleted_vals; - serializer(bitmap_deleted_vals); - - for (const value_type &value : *this) { - serializer(value); - } - } - - template - static sparse_array deserialize_hash_compatible(Deserializer &deserializer, - Allocator &alloc) { - const slz_size_type sparse_bucket_size = - deserialize_value(deserializer); - const slz_size_type bitmap_vals = - deserialize_value(deserializer); - const slz_size_type bitmap_deleted_vals = - deserialize_value(deserializer); - - if (sparse_bucket_size > BITMAP_NB_BITS) { - TSL_SH_THROW_OR_ABORT( - std::runtime_error, - "Deserialized sparse_bucket_size is too big for the platform. " - "Maximum should be BITMAP_NB_BITS."); - } - - sparse_array sarray; - if (sparse_bucket_size == 0) { - return sarray; - } - - sarray.m_bitmap_vals = numeric_cast( - bitmap_vals, "Deserialized bitmap_vals is too big."); - sarray.m_bitmap_deleted_vals = numeric_cast( - bitmap_deleted_vals, "Deserialized bitmap_deleted_vals is too big."); - - sarray.m_capacity = numeric_cast( - sparse_bucket_size, "Deserialized sparse_bucket_size is too big."); - sarray.m_values = alloc.allocate(sarray.m_capacity); - - TSL_SH_TRY { - for (size_type ivalue = 0; ivalue < sarray.m_capacity; ivalue++) { - construct_value(alloc, sarray.m_values + ivalue, - deserialize_value(deserializer)); - sarray.m_nb_elements++; - } - } - TSL_SH_CATCH(...) { - sarray.clear(alloc); - TSL_SH_RETRHOW; - } - - return sarray; - } - - /** - * Deserialize the values of the bucket and insert them all in sparse_hash - * through sparse_hash.insert(...). - */ - template - static void deserialize_values_into_sparse_hash(Deserializer &deserializer, - SparseHash &sparse_hash) { - const slz_size_type sparse_bucket_size = - deserialize_value(deserializer); - - const slz_size_type bitmap_vals = - deserialize_value(deserializer); - static_cast(bitmap_vals); // Ignore, not needed - - const slz_size_type bitmap_deleted_vals = - deserialize_value(deserializer); - static_cast(bitmap_deleted_vals); // Ignore, not needed - - for (slz_size_type ivalue = 0; ivalue < sparse_bucket_size; ivalue++) { - sparse_hash.insert(deserialize_value(deserializer)); - } - } - - private: - template - static void construct_value(allocator_type &alloc, value_type *value, - Args &&...value_args) { - std::allocator_traits::construct( - alloc, value, std::forward(value_args)...); - } - - static void destroy_value(allocator_type &alloc, value_type *value) noexcept { - std::allocator_traits::destroy(alloc, value); - } - - static void destroy_and_deallocate_values( - allocator_type &alloc, value_type *values, size_type nb_values, - size_type capacity_values) noexcept { - for (size_type i = 0; i < nb_values; i++) { - destroy_value(alloc, values + i); - } - - alloc.deallocate(values, capacity_values); - } - - static size_type popcount(bitmap_type val) noexcept { - if (sizeof(bitmap_type) <= sizeof(unsigned int)) { - return static_cast( - tsl::detail_popcount::popcount(static_cast(val))); - } else { - return static_cast(tsl::detail_popcount::popcountll(val)); - } - } - - size_type index_to_offset(size_type index) const noexcept { - tsl_sh_assert(index < BITMAP_NB_BITS); - return popcount(m_bitmap_vals & - ((bitmap_type(1) << index) - bitmap_type(1))); - } - - // TODO optimize - size_type offset_to_index(size_type offset) const noexcept { - tsl_sh_assert(offset < m_nb_elements); - - bitmap_type bitmap_vals = m_bitmap_vals; - size_type index = 0; - size_type nb_ones = 0; - - while (bitmap_vals != 0) { - if ((bitmap_vals & 0x1) == 1) { - if (nb_ones == offset) { - break; - } - - nb_ones++; - } - - index++; - bitmap_vals = bitmap_vals >> 1; - } - - return index; - } - - size_type next_capacity() const noexcept { - return static_cast(m_capacity + CAPACITY_GROWTH_STEP); - } - - /** - * Insertion - * - * Two situations: - * - Either we are in a situation where - * std::is_nothrow_move_constructible::value is true. In this - * case, on insertion we just reallocate m_values when we reach its capacity - * (i.e. m_nb_elements == m_capacity), otherwise we just put the new value at - * its appropriate place. We can easily keep the strong exception guarantee as - * moving the values around is safe. - * - Otherwise we are in a situation where - * std::is_nothrow_move_constructible::value is false. In this - * case on EACH insertion we allocate a new area of m_nb_elements + 1 where we - * copy the values of m_values into it and put the new value there. On - * success, we set m_values to this new area. Even if slower, it's the only - * way to preserve to strong exception guarantee. - */ - template ::value>::type * = nullptr> - void insert_at_offset(allocator_type &alloc, size_type offset, - Args &&...value_args) { - if (m_nb_elements < m_capacity) { - insert_at_offset_no_realloc(alloc, offset, - std::forward(value_args)...); - } else { - insert_at_offset_realloc(alloc, offset, next_capacity(), - std::forward(value_args)...); - } - } - - template ::value>::type * = nullptr> - void insert_at_offset(allocator_type &alloc, size_type offset, - Args &&...value_args) { - insert_at_offset_realloc(alloc, offset, m_nb_elements + 1, - std::forward(value_args)...); - } - - template ::value>::type * = nullptr> - void insert_at_offset_no_realloc(allocator_type &alloc, size_type offset, - Args &&...value_args) { - tsl_sh_assert(offset <= m_nb_elements); - tsl_sh_assert(m_nb_elements < m_capacity); - - for (size_type i = m_nb_elements; i > offset; i--) { - construct_value(alloc, m_values + i, std::move(m_values[i - 1])); - destroy_value(alloc, m_values + i - 1); - } - - TSL_SH_TRY { - construct_value(alloc, m_values + offset, - std::forward(value_args)...); - } - TSL_SH_CATCH(...) { - for (size_type i = offset; i < m_nb_elements; i++) { - construct_value(alloc, m_values + i, std::move(m_values[i + 1])); - destroy_value(alloc, m_values + i + 1); - } - TSL_SH_RETRHOW; - } - } - - template ::value>::type * = nullptr> - void insert_at_offset_realloc(allocator_type &alloc, size_type offset, - size_type new_capacity, Args &&...value_args) { - tsl_sh_assert(new_capacity > m_nb_elements); - - value_type *new_values = alloc.allocate(new_capacity); - // Allocate should throw if there is a failure - tsl_sh_assert(new_values != nullptr); - - TSL_SH_TRY { - construct_value(alloc, new_values + offset, - std::forward(value_args)...); - } - TSL_SH_CATCH(...) { - alloc.deallocate(new_values, new_capacity); - TSL_SH_RETRHOW; - } - - // Should not throw from here - for (size_type i = 0; i < offset; i++) { - construct_value(alloc, new_values + i, std::move(m_values[i])); - } - - for (size_type i = offset; i < m_nb_elements; i++) { - construct_value(alloc, new_values + i + 1, std::move(m_values[i])); - } - - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); - - m_values = new_values; - m_capacity = new_capacity; - } - - template ::value>::type * = nullptr> - void insert_at_offset_realloc(allocator_type &alloc, size_type offset, - size_type new_capacity, Args &&...value_args) { - tsl_sh_assert(new_capacity > m_nb_elements); - - value_type *new_values = alloc.allocate(new_capacity); - // Allocate should throw if there is a failure - tsl_sh_assert(new_values != nullptr); - - size_type nb_new_values = 0; - TSL_SH_TRY { - for (size_type i = 0; i < offset; i++) { - construct_value(alloc, new_values + i, m_values[i]); - nb_new_values++; - } - - construct_value(alloc, new_values + offset, - std::forward(value_args)...); - nb_new_values++; - - for (size_type i = offset; i < m_nb_elements; i++) { - construct_value(alloc, new_values + i + 1, m_values[i]); - nb_new_values++; - } - } - TSL_SH_CATCH(...) { - destroy_and_deallocate_values(alloc, new_values, nb_new_values, - new_capacity); - TSL_SH_RETRHOW; - } - - tsl_sh_assert(nb_new_values == m_nb_elements + 1); - - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); - - m_values = new_values; - m_capacity = new_capacity; - } - - /** - * Erasure - * - * Two situations: - * - Either we are in a situation where - * std::is_nothrow_move_constructible::value is true. Simply - * destroy the value and left-shift move the value on the right of offset. - * - Otherwise we are in a situation where - * std::is_nothrow_move_constructible::value is false. Copy all - * the values except the one at offset into a new heap area. On success, we - * set m_values to this new area. Even if slower, it's the only way to - * preserve to strong exception guarantee. - */ - template ::value>::type * = nullptr> - void erase_at_offset(allocator_type &alloc, size_type offset) noexcept { - tsl_sh_assert(offset < m_nb_elements); - - destroy_value(alloc, m_values + offset); - - for (size_type i = offset + 1; i < m_nb_elements; i++) { - construct_value(alloc, m_values + i - 1, std::move(m_values[i])); - destroy_value(alloc, m_values + i); - } - } - - template ::value>::type * = nullptr> - void erase_at_offset(allocator_type &alloc, size_type offset) { - tsl_sh_assert(offset < m_nb_elements); - - // Erasing the last element, don't need to reallocate. We keep the capacity. - if (offset + 1 == m_nb_elements) { - destroy_value(alloc, m_values + offset); - return; - } - - tsl_sh_assert(m_nb_elements > 1); - const size_type new_capacity = m_nb_elements - 1; - - value_type *new_values = alloc.allocate(new_capacity); - // Allocate should throw if there is a failure - tsl_sh_assert(new_values != nullptr); - - size_type nb_new_values = 0; - TSL_SH_TRY { - for (size_type i = 0; i < m_nb_elements; i++) { - if (i != offset) { - construct_value(alloc, new_values + nb_new_values, m_values[i]); - nb_new_values++; - } - } - } - TSL_SH_CATCH(...) { - destroy_and_deallocate_values(alloc, new_values, nb_new_values, - new_capacity); - TSL_SH_RETRHOW; - } - - tsl_sh_assert(nb_new_values == m_nb_elements - 1); - - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); - - m_values = new_values; - m_capacity = new_capacity; - } - - private: - value_type *m_values; - - bitmap_type m_bitmap_vals; - bitmap_type m_bitmap_deleted_vals; - - size_type m_nb_elements; - size_type m_capacity; - bool m_last_array; -}; - -/** - * Internal common class used by `sparse_map` and `sparse_set`. - * - * `ValueType` is what will be stored by `sparse_hash` (usually `std::pair` for map and `Key` for set). - * - * `KeySelect` should be a `FunctionObject` which takes a `ValueType` in - * parameter and returns a reference to the key. - * - * `ValueSelect` should be a `FunctionObject` which takes a `ValueType` in - * parameter and returns a reference to the value. `ValueSelect` should be void - * if there is no value (in a set for example). - * - * The strong exception guarantee only holds if `ExceptionSafety` is set to - * `tsl::sh::exception_safety::strong`. - * - * `ValueType` must be nothrow move constructible and/or copy constructible. - * Behaviour is undefined if the destructor of `ValueType` throws. - * - * - * The class holds its buckets in a 2-dimensional fashion. Instead of having a - * linear `std::vector` for [0, bucket_count) where each bucket stores - * one value, we have a `std::vector` (m_sparse_buckets_data) - * where each `sparse_array` stores multiple values (up to - * `sparse_array::BITMAP_NB_BITS`). To convert a one dimensional `ibucket` - * position to a position in `std::vector` and a position in - * `sparse_array`, use respectively the methods - * `sparse_array::sparse_ibucket(ibucket)` and - * `sparse_array::index_in_sparse_bucket(ibucket)`. - */ -template -class sparse_hash : private Allocator, - private Hash, - private KeyEqual, - private GrowthPolicy { - private: - template - using has_mapped_type = - typename std::integral_constant::value>; - - static_assert( - noexcept(std::declval().bucket_for_hash(std::size_t(0))), - "GrowthPolicy::bucket_for_hash must be noexcept."); - static_assert(noexcept(std::declval().clear()), - "GrowthPolicy::clear must be noexcept."); - - public: - template - class sparse_iterator; - - using key_type = typename KeySelect::key_type; - using value_type = ValueType; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using hasher = Hash; - using key_equal = KeyEqual; - using allocator_type = Allocator; - using reference = value_type &; - using const_reference = const value_type &; - using pointer = value_type *; - using const_pointer = const value_type *; - using iterator = sparse_iterator; - using const_iterator = sparse_iterator; - - private: - using sparse_array = - tsl::detail_sparse_hash::sparse_array; - - using sparse_buckets_allocator = typename std::allocator_traits< - allocator_type>::template rebind_alloc; - using sparse_buckets_container = - std::vector; - - public: - /** - * The `operator*()` and `operator->()` methods return a const reference and - * const pointer respectively to the stored value type (`Key` for a set, - * `std::pair` for a map). - * - * In case of a map, to get a mutable reference to the value `T` associated to - * a key (the `.second` in the stored pair), you have to call `value()`. - */ - template - class sparse_iterator { - friend class sparse_hash; - - private: - using sparse_bucket_iterator = typename std::conditional< - IsConst, typename sparse_buckets_container::const_iterator, - typename sparse_buckets_container::iterator>::type; - - using sparse_array_iterator = - typename std::conditional::type; - - /** - * sparse_array_it should be nullptr if sparse_bucket_it == - * m_sparse_buckets_data.end(). (TODO better way?) - */ - sparse_iterator(sparse_bucket_iterator sparse_bucket_it, - sparse_array_iterator sparse_array_it) - : m_sparse_buckets_it(sparse_bucket_it), - m_sparse_array_it(sparse_array_it) {} - - public: - using iterator_category = std::forward_iterator_tag; - using value_type = const typename sparse_hash::value_type; - using difference_type = std::ptrdiff_t; - using reference = value_type &; - using pointer = value_type *; - - sparse_iterator() noexcept {} - - // Copy constructor from iterator to const_iterator. - template ::type * = nullptr> - sparse_iterator(const sparse_iterator &other) noexcept - : m_sparse_buckets_it(other.m_sparse_buckets_it), - m_sparse_array_it(other.m_sparse_array_it) {} - - sparse_iterator(const sparse_iterator &other) = default; - sparse_iterator(sparse_iterator &&other) = default; - sparse_iterator &operator=(const sparse_iterator &other) = default; - sparse_iterator &operator=(sparse_iterator &&other) = default; - - const typename sparse_hash::key_type &key() const { - return KeySelect()(*m_sparse_array_it); - } - - template ::value && - IsConst>::type * = nullptr> - const typename U::value_type &value() const { - return U()(*m_sparse_array_it); - } - - template ::value && - !IsConst>::type * = nullptr> - typename U::value_type &value() { - return U()(*m_sparse_array_it); - } - - reference operator*() const { return *m_sparse_array_it; } - - pointer operator->() const { return std::addressof(*m_sparse_array_it); } - - sparse_iterator &operator++() { - tsl_sh_assert(m_sparse_array_it != nullptr); - ++m_sparse_array_it; - - if (m_sparse_array_it == m_sparse_buckets_it->end()) { - do { - if (m_sparse_buckets_it->last()) { - ++m_sparse_buckets_it; - m_sparse_array_it = nullptr; - return *this; - } - - ++m_sparse_buckets_it; - } while (m_sparse_buckets_it->empty()); - - m_sparse_array_it = m_sparse_buckets_it->begin(); - } - - return *this; - } - - sparse_iterator operator++(int) { - sparse_iterator tmp(*this); - ++*this; - - return tmp; - } - - friend bool operator==(const sparse_iterator &lhs, - const sparse_iterator &rhs) { - return lhs.m_sparse_buckets_it == rhs.m_sparse_buckets_it && - lhs.m_sparse_array_it == rhs.m_sparse_array_it; - } - - friend bool operator!=(const sparse_iterator &lhs, - const sparse_iterator &rhs) { - return !(lhs == rhs); - } - - private: - sparse_bucket_iterator m_sparse_buckets_it; - sparse_array_iterator m_sparse_array_it; - }; - - public: - sparse_hash(size_type bucket_count, const Hash &hash, const KeyEqual &equal, - const Allocator &alloc, float max_load_factor) - : Allocator(alloc), - Hash(hash), - KeyEqual(equal), - GrowthPolicy(bucket_count), - m_sparse_buckets_data(alloc), - m_sparse_buckets(static_empty_sparse_bucket_ptr()), - m_bucket_count(bucket_count), - m_nb_elements(0), - m_nb_deleted_buckets(0) { - if (m_bucket_count > max_bucket_count()) { - TSL_SH_THROW_OR_ABORT(std::length_error, - "The map exceeds its maximum size."); - } - - if (m_bucket_count > 0) { - /* - * We can't use the `vector(size_type count, const Allocator& alloc)` - * constructor as it's only available in C++14 and we need to support - * C++11. We thus must resize after using the `vector(const Allocator& - * alloc)` constructor. - * - * We can't use `vector(size_type count, const T& value, const Allocator& - * alloc)` as it requires the value T to be copyable. - */ - m_sparse_buckets_data.resize( - sparse_array::nb_sparse_buckets(bucket_count)); - m_sparse_buckets = m_sparse_buckets_data.data(); - - tsl_sh_assert(!m_sparse_buckets_data.empty()); - m_sparse_buckets_data.back().set_as_last(); - } - - this->max_load_factor(max_load_factor); - - // Check in the constructor instead of outside of a function to avoid - // compilation issues when value_type is not complete. - static_assert(std::is_nothrow_move_constructible::value || - std::is_copy_constructible::value, - "Key, and T if present, must be nothrow move constructible " - "and/or copy constructible."); - } - - ~sparse_hash() { clear(); } - - sparse_hash(const sparse_hash &other) - : Allocator(std::allocator_traits< - Allocator>::select_on_container_copy_construction(other)), - Hash(other), - KeyEqual(other), - GrowthPolicy(other), - m_sparse_buckets_data( - std::allocator_traits< - Allocator>::select_on_container_copy_construction(other)), - m_bucket_count(other.m_bucket_count), - m_nb_elements(other.m_nb_elements), - m_nb_deleted_buckets(other.m_nb_deleted_buckets), - m_load_threshold_rehash(other.m_load_threshold_rehash), - m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), - m_max_load_factor(other.m_max_load_factor) { - copy_buckets_from(other), - m_sparse_buckets = m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data(); - } - - sparse_hash(sparse_hash &&other) noexcept( - std::is_nothrow_move_constructible::value - &&std::is_nothrow_move_constructible::value - &&std::is_nothrow_move_constructible::value - &&std::is_nothrow_move_constructible::value - &&std::is_nothrow_move_constructible< - sparse_buckets_container>::value) - : Allocator(std::move(other)), - Hash(std::move(other)), - KeyEqual(std::move(other)), - GrowthPolicy(std::move(other)), - m_sparse_buckets_data(std::move(other.m_sparse_buckets_data)), - m_sparse_buckets(m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data()), - m_bucket_count(other.m_bucket_count), - m_nb_elements(other.m_nb_elements), - m_nb_deleted_buckets(other.m_nb_deleted_buckets), - m_load_threshold_rehash(other.m_load_threshold_rehash), - m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), - m_max_load_factor(other.m_max_load_factor) { - other.GrowthPolicy::clear(); - other.m_sparse_buckets_data.clear(); - other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); - other.m_bucket_count = 0; - other.m_nb_elements = 0; - other.m_nb_deleted_buckets = 0; - other.m_load_threshold_rehash = 0; - other.m_load_threshold_clear_deleted = 0; - } - - sparse_hash &operator=(const sparse_hash &other) { - if (this != &other) { - clear(); - - if (std::allocator_traits< - Allocator>::propagate_on_container_copy_assignment::value) { - Allocator::operator=(other); - } - - Hash::operator=(other); - KeyEqual::operator=(other); - GrowthPolicy::operator=(other); - - if (std::allocator_traits< - Allocator>::propagate_on_container_copy_assignment::value) { - m_sparse_buckets_data = - sparse_buckets_container(static_cast(other)); - } else { - if (m_sparse_buckets_data.size() != - other.m_sparse_buckets_data.size()) { - m_sparse_buckets_data = - sparse_buckets_container(static_cast(*this)); - } else { - m_sparse_buckets_data.clear(); - } - } - - copy_buckets_from(other); - m_sparse_buckets = m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data(); - - m_bucket_count = other.m_bucket_count; - m_nb_elements = other.m_nb_elements; - m_nb_deleted_buckets = other.m_nb_deleted_buckets; - m_load_threshold_rehash = other.m_load_threshold_rehash; - m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; - m_max_load_factor = other.m_max_load_factor; - } - - return *this; - } - - sparse_hash &operator=(sparse_hash &&other) { - clear(); - - if (std::allocator_traits< - Allocator>::propagate_on_container_move_assignment::value) { - static_cast(*this) = - std::move(static_cast(other)); - m_sparse_buckets_data = std::move(other.m_sparse_buckets_data); - } else if (static_cast(*this) != - static_cast(other)) { - move_buckets_from(std::move(other)); - } else { - static_cast(*this) = - std::move(static_cast(other)); - m_sparse_buckets_data = std::move(other.m_sparse_buckets_data); - } - - m_sparse_buckets = m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data(); - - static_cast(*this) = std::move(static_cast(other)); - static_cast(*this) = std::move(static_cast(other)); - static_cast(*this) = - std::move(static_cast(other)); - m_bucket_count = other.m_bucket_count; - m_nb_elements = other.m_nb_elements; - m_nb_deleted_buckets = other.m_nb_deleted_buckets; - m_load_threshold_rehash = other.m_load_threshold_rehash; - m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; - m_max_load_factor = other.m_max_load_factor; - - other.GrowthPolicy::clear(); - other.m_sparse_buckets_data.clear(); - other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); - other.m_bucket_count = 0; - other.m_nb_elements = 0; - other.m_nb_deleted_buckets = 0; - other.m_load_threshold_rehash = 0; - other.m_load_threshold_clear_deleted = 0; - - return *this; - } - - allocator_type get_allocator() const { - return static_cast(*this); - } - - /* - * Iterators - */ - iterator begin() noexcept { - auto begin = m_sparse_buckets_data.begin(); - while (begin != m_sparse_buckets_data.end() && begin->empty()) { - ++begin; - } - - return iterator(begin, (begin != m_sparse_buckets_data.end()) - ? begin->begin() - : nullptr); - } - - const_iterator begin() const noexcept { return cbegin(); } - - const_iterator cbegin() const noexcept { - auto begin = m_sparse_buckets_data.cbegin(); - while (begin != m_sparse_buckets_data.cend() && begin->empty()) { - ++begin; - } - - return const_iterator(begin, (begin != m_sparse_buckets_data.cend()) - ? begin->cbegin() - : nullptr); - } - - iterator end() noexcept { - return iterator(m_sparse_buckets_data.end(), nullptr); - } - - const_iterator end() const noexcept { return cend(); } - - const_iterator cend() const noexcept { - return const_iterator(m_sparse_buckets_data.cend(), nullptr); - } - - /* - * Capacity - */ - bool empty() const noexcept { return m_nb_elements == 0; } - - size_type size() const noexcept { return m_nb_elements; } - - size_type max_size() const noexcept { - return std::min(std::allocator_traits::max_size(), - m_sparse_buckets_data.max_size()); - } - - /* - * Modifiers - */ - void clear() noexcept { - for (auto &bucket : m_sparse_buckets_data) { - bucket.clear(*this); - } - - m_nb_elements = 0; - m_nb_deleted_buckets = 0; - } - - template - std::pair insert(P &&value) { - return insert_impl(KeySelect()(value), std::forward

(value)); - } - - template - iterator insert_hint(const_iterator hint, P &&value) { - if (hint != cend() && - compare_keys(KeySelect()(*hint), KeySelect()(value))) { - return mutable_iterator(hint); - } - - return insert(std::forward

(value)).first; - } - - template - void insert(InputIt first, InputIt last) { - if (std::is_base_of< - std::forward_iterator_tag, - typename std::iterator_traits::iterator_category>::value) { - const auto nb_elements_insert = std::distance(first, last); - const size_type nb_free_buckets = m_load_threshold_rehash - size(); - tsl_sh_assert(m_load_threshold_rehash >= size()); - - if (nb_elements_insert > 0 && - nb_free_buckets < size_type(nb_elements_insert)) { - reserve(size() + size_type(nb_elements_insert)); - } - } - - for (; first != last; ++first) { - insert(*first); - } - } - - template - std::pair insert_or_assign(K &&key, M &&obj) { - auto it = try_emplace(std::forward(key), std::forward(obj)); - if (!it.second) { - it.first.value() = std::forward(obj); - } - - return it; - } - - template - iterator insert_or_assign(const_iterator hint, K &&key, M &&obj) { - if (hint != cend() && compare_keys(KeySelect()(*hint), key)) { - auto it = mutable_iterator(hint); - it.value() = std::forward(obj); - - return it; - } - - return insert_or_assign(std::forward(key), std::forward(obj)).first; - } - - template - std::pair emplace(Args &&...args) { - return insert(value_type(std::forward(args)...)); - } - - template - iterator emplace_hint(const_iterator hint, Args &&...args) { - return insert_hint(hint, value_type(std::forward(args)...)); - } - - template - std::pair try_emplace(K &&key, Args &&...args) { - return insert_impl(key, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(args)...)); - } - - template - iterator try_emplace_hint(const_iterator hint, K &&key, Args &&...args) { - if (hint != cend() && compare_keys(KeySelect()(*hint), key)) { - return mutable_iterator(hint); - } - - return try_emplace(std::forward(key), std::forward(args)...).first; - } - - /** - * Here to avoid `template size_type erase(const K& key)` being used - * when we use an iterator instead of a const_iterator. - */ - iterator erase(iterator pos) { - tsl_sh_assert(pos != end() && m_nb_elements > 0); - auto it_sparse_array_next = - pos.m_sparse_buckets_it->erase(*this, pos.m_sparse_array_it); - m_nb_elements--; - m_nb_deleted_buckets++; - - if (it_sparse_array_next == pos.m_sparse_buckets_it->end()) { - auto it_sparse_buckets_next = pos.m_sparse_buckets_it; - do { - ++it_sparse_buckets_next; - } while (it_sparse_buckets_next != m_sparse_buckets_data.end() && - it_sparse_buckets_next->empty()); - - if (it_sparse_buckets_next == m_sparse_buckets_data.end()) { - return end(); - } else { - return iterator(it_sparse_buckets_next, - it_sparse_buckets_next->begin()); - } - } else { - return iterator(pos.m_sparse_buckets_it, it_sparse_array_next); - } - } - - iterator erase(const_iterator pos) { return erase(mutable_iterator(pos)); } - - iterator erase(const_iterator first, const_iterator last) { - if (first == last) { - return mutable_iterator(first); - } - - // TODO Optimize, could avoid the call to std::distance. - const size_type nb_elements_to_erase = - static_cast(std::distance(first, last)); - auto to_delete = mutable_iterator(first); - for (size_type i = 0; i < nb_elements_to_erase; i++) { - to_delete = erase(to_delete); - } - - return to_delete; - } - - template - size_type erase(const K &key) { - return erase(key, hash_key(key)); - } - - template - size_type erase(const K &key, std::size_t hash) { - return erase_impl(key, hash); - } - - void swap(sparse_hash &other) { - using std::swap; - - if (std::allocator_traits::propagate_on_container_swap::value) { - swap(static_cast(*this), static_cast(other)); - } else { - tsl_sh_assert(static_cast(*this) == - static_cast(other)); - } - - swap(static_cast(*this), static_cast(other)); - swap(static_cast(*this), static_cast(other)); - swap(static_cast(*this), - static_cast(other)); - swap(m_sparse_buckets_data, other.m_sparse_buckets_data); - swap(m_sparse_buckets, other.m_sparse_buckets); - swap(m_bucket_count, other.m_bucket_count); - swap(m_nb_elements, other.m_nb_elements); - swap(m_nb_deleted_buckets, other.m_nb_deleted_buckets); - swap(m_load_threshold_rehash, other.m_load_threshold_rehash); - swap(m_load_threshold_clear_deleted, other.m_load_threshold_clear_deleted); - swap(m_max_load_factor, other.m_max_load_factor); - } - - /* - * Lookup - */ - template < - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - typename U::value_type &at(const K &key) { - return at(key, hash_key(key)); - } - - template < - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - typename U::value_type &at(const K &key, std::size_t hash) { - return const_cast( - static_cast(this)->at(key, hash)); - } - - template < - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - const typename U::value_type &at(const K &key) const { - return at(key, hash_key(key)); - } - - template < - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - const typename U::value_type &at(const K &key, std::size_t hash) const { - auto it = find(key, hash); - if (it != cend()) { - return it.value(); - } else { - TSL_SH_THROW_OR_ABORT(std::out_of_range, "Couldn't find key."); - } - } - - template < - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - typename U::value_type &operator[](K &&key) { - return try_emplace(std::forward(key)).first.value(); - } - - template - bool contains(const K &key) const { - return contains(key, hash_key(key)); - } - - template - bool contains(const K &key, std::size_t hash) const { - return count(key, hash) != 0; - } - - template - size_type count(const K &key) const { - return count(key, hash_key(key)); - } - - template - size_type count(const K &key, std::size_t hash) const { - if (find(key, hash) != cend()) { - return 1; - } else { - return 0; - } - } - - template - iterator find(const K &key) { - return find_impl(key, hash_key(key)); - } - - template - iterator find(const K &key, std::size_t hash) { - return find_impl(key, hash); - } - - template - const_iterator find(const K &key) const { - return find_impl(key, hash_key(key)); - } - - template - const_iterator find(const K &key, std::size_t hash) const { - return find_impl(key, hash); - } - - template - std::pair equal_range(const K &key) { - return equal_range(key, hash_key(key)); - } - - template - std::pair equal_range(const K &key, std::size_t hash) { - iterator it = find(key, hash); - return std::make_pair(it, (it == end()) ? it : std::next(it)); - } - - template - std::pair equal_range(const K &key) const { - return equal_range(key, hash_key(key)); - } - - template - std::pair equal_range( - const K &key, std::size_t hash) const { - const_iterator it = find(key, hash); - return std::make_pair(it, (it == cend()) ? it : std::next(it)); - } - - /* - * Bucket interface - */ - size_type bucket_count() const { return m_bucket_count; } - - size_type max_bucket_count() const { - return m_sparse_buckets_data.max_size(); - } - - /* - * Hash policy - */ - float load_factor() const { - if (bucket_count() == 0) { - return 0; - } - - return float(m_nb_elements) / float(bucket_count()); - } - - float max_load_factor() const { return m_max_load_factor; } - - void max_load_factor(float ml) { - m_max_load_factor = std::max(0.1f, std::min(ml, 0.8f)); - m_load_threshold_rehash = - size_type(float(bucket_count()) * m_max_load_factor); - - const float max_load_factor_with_deleted_buckets = - m_max_load_factor + 0.5f * (1.0f - m_max_load_factor); - tsl_sh_assert(max_load_factor_with_deleted_buckets > 0.0f && - max_load_factor_with_deleted_buckets <= 1.0f); - m_load_threshold_clear_deleted = - size_type(float(bucket_count()) * max_load_factor_with_deleted_buckets); - } - - void rehash(size_type count) { - count = std::max(count, - size_type(std::ceil(float(size()) / max_load_factor()))); - rehash_impl(count); - } - - void reserve(size_type count) { - rehash(size_type(std::ceil(float(count) / max_load_factor()))); - } - - /* - * Observers - */ - hasher hash_function() const { return static_cast(*this); } - - key_equal key_eq() const { return static_cast(*this); } - - /* - * Other - */ - iterator mutable_iterator(const_iterator pos) { - auto it_sparse_buckets = - m_sparse_buckets_data.begin() + - std::distance(m_sparse_buckets_data.cbegin(), pos.m_sparse_buckets_it); - - return iterator(it_sparse_buckets, - sparse_array::mutable_iterator(pos.m_sparse_array_it)); - } - - template - void serialize(Serializer &serializer) const { - serialize_impl(serializer); - } - - template - void deserialize(Deserializer &deserializer, bool hash_compatible) { - deserialize_impl(deserializer, hash_compatible); - } - - private: - template - std::size_t hash_key(const K &key) const { - return Hash::operator()(key); - } - - template - bool compare_keys(const K1 &key1, const K2 &key2) const { - return KeyEqual::operator()(key1, key2); - } - - size_type bucket_for_hash(std::size_t hash) const { - const std::size_t bucket = GrowthPolicy::bucket_for_hash(hash); - tsl_sh_assert(sparse_array::sparse_ibucket(bucket) < - m_sparse_buckets_data.size() || - (bucket == 0 && m_sparse_buckets_data.empty())); - - return bucket; - } - - template ::value>::type * = - nullptr> - size_type next_bucket(size_type ibucket, size_type iprobe) const { - (void)iprobe; - if (Probing == tsl::sh::probing::linear) { - return (ibucket + 1) & this->m_mask; - } else { - tsl_sh_assert(Probing == tsl::sh::probing::quadratic); - return (ibucket + iprobe) & this->m_mask; - } - } - - template ::value>::type * = - nullptr> - size_type next_bucket(size_type ibucket, size_type iprobe) const { - (void)iprobe; - if (Probing == tsl::sh::probing::linear) { - ibucket++; - return (ibucket != bucket_count()) ? ibucket : 0; - } else { - tsl_sh_assert(Probing == tsl::sh::probing::quadratic); - ibucket += iprobe; - return (ibucket < bucket_count()) ? ibucket : ibucket % bucket_count(); - } - } - - // TODO encapsulate m_sparse_buckets_data to avoid the managing the allocator - void copy_buckets_from(const sparse_hash &other) { - m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); - - TSL_SH_TRY { - for (const auto &bucket : other.m_sparse_buckets_data) { - m_sparse_buckets_data.emplace_back(bucket, - static_cast(*this)); - } - } - TSL_SH_CATCH(...) { - clear(); - TSL_SH_RETRHOW; - } - - tsl_sh_assert(m_sparse_buckets_data.empty() || - m_sparse_buckets_data.back().last()); - } - - void move_buckets_from(sparse_hash &&other) { - m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); - - TSL_SH_TRY { - for (auto &&bucket : other.m_sparse_buckets_data) { - m_sparse_buckets_data.emplace_back(std::move(bucket), - static_cast(*this)); - } - } - TSL_SH_CATCH(...) { - clear(); - TSL_SH_RETRHOW; - } - - tsl_sh_assert(m_sparse_buckets_data.empty() || - m_sparse_buckets_data.back().last()); - } - - template - std::pair insert_impl(const K &key, - Args &&...value_type_args) { - /** - * We must insert the value in the first empty or deleted bucket we find. If - * we first find a deleted bucket, we still have to continue the search - * until we find an empty bucket or until we have searched all the buckets - * to be sure that the value is not in the hash table. We thus remember the - * position, if any, of the first deleted bucket we have encountered so we - * can insert it there if needed. - */ - bool found_first_deleted_bucket = false; - std::size_t sparse_ibucket_first_deleted = 0; - typename sparse_array::size_type index_in_sparse_bucket_first_deleted = 0; - - const std::size_t hash = hash_key(key); - std::size_t ibucket = bucket_for_hash(hash); - - std::size_t probe = 0; - while (true) { - std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); - - if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = - m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (compare_keys(key, KeySelect()(*value_it))) { - return std::make_pair( - iterator(m_sparse_buckets_data.begin() + sparse_ibucket, - value_it), - false); - } - } else if (m_sparse_buckets[sparse_ibucket].has_deleted_value( - index_in_sparse_bucket) && - probe < m_bucket_count) { - if (!found_first_deleted_bucket) { - found_first_deleted_bucket = true; - sparse_ibucket_first_deleted = sparse_ibucket; - index_in_sparse_bucket_first_deleted = index_in_sparse_bucket; - } - } else { - /** - * At this point we are sure that the value does not exist - * in the hash table. - * First check if we satisfy load and delete thresholds, and if not, - * rehash the hash table (and therefore start over). Otherwise, just - * insert the value into the appropriate bucket. - */ - if (size() >= m_load_threshold_rehash) { - rehash_impl(GrowthPolicy::next_bucket_count()); - return insert_impl(key, std::forward(value_type_args)...); - } else if (size() + m_nb_deleted_buckets >= - m_load_threshold_clear_deleted) { - clear_deleted_buckets(); - return insert_impl(key, std::forward(value_type_args)...); - } - - if (found_first_deleted_bucket) { - auto it = insert_in_bucket(sparse_ibucket_first_deleted, - index_in_sparse_bucket_first_deleted, - std::forward(value_type_args)...); - m_nb_deleted_buckets--; - - return it; - } - - return insert_in_bucket(sparse_ibucket, index_in_sparse_bucket, - std::forward(value_type_args)...); - } - - probe++; - ibucket = next_bucket(ibucket, probe); - } - } - - template - std::pair insert_in_bucket( - std::size_t sparse_ibucket, - typename sparse_array::size_type index_in_sparse_bucket, - Args &&...value_type_args) { - auto value_it = m_sparse_buckets[sparse_ibucket].set( - *this, index_in_sparse_bucket, std::forward(value_type_args)...); - m_nb_elements++; - - return std::make_pair( - iterator(m_sparse_buckets_data.begin() + sparse_ibucket, value_it), - true); - } - - template - size_type erase_impl(const K &key, std::size_t hash) { - std::size_t ibucket = bucket_for_hash(hash); - - std::size_t probe = 0; - while (true) { - const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - const auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); - - if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = - m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (compare_keys(key, KeySelect()(*value_it))) { - m_sparse_buckets[sparse_ibucket].erase(*this, value_it, - index_in_sparse_bucket); - m_nb_elements--; - m_nb_deleted_buckets++; - - return 1; - } - } else if (!m_sparse_buckets[sparse_ibucket].has_deleted_value( - index_in_sparse_bucket) || - probe >= m_bucket_count) { - return 0; - } - - probe++; - ibucket = next_bucket(ibucket, probe); - } - } - - template - iterator find_impl(const K &key, std::size_t hash) { - return mutable_iterator( - static_cast(this)->find(key, hash)); - } - - template - const_iterator find_impl(const K &key, std::size_t hash) const { - std::size_t ibucket = bucket_for_hash(hash); - - std::size_t probe = 0; - while (true) { - const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - const auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); - - if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = - m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (compare_keys(key, KeySelect()(*value_it))) { - return const_iterator(m_sparse_buckets_data.cbegin() + sparse_ibucket, - value_it); - } - } else if (!m_sparse_buckets[sparse_ibucket].has_deleted_value( - index_in_sparse_bucket) || - probe >= m_bucket_count) { - return cend(); - } - - probe++; - ibucket = next_bucket(ibucket, probe); - } - } - - void clear_deleted_buckets() { - // TODO could be optimized, we could do it in-place instead of allocating a - // new bucket array. - rehash_impl(m_bucket_count); - tsl_sh_assert(m_nb_deleted_buckets == 0); - } - - template ::type - * = nullptr> - void rehash_impl(size_type count) { - sparse_hash new_table(count, static_cast(*this), - static_cast(*this), - static_cast(*this), m_max_load_factor); - - for (auto &bucket : m_sparse_buckets_data) { - for (auto &val : bucket) { - new_table.insert_on_rehash(std::move(val)); - } - - // TODO try to reuse some of the memory - bucket.clear(*this); - } - - new_table.swap(*this); - } - - /** - * TODO: For now we copy each element into the new map. We could move - * them if they are nothrow_move_constructible without triggering - * any exception if we reserve enough space in the sparse arrays beforehand. - */ - template ::type * = nullptr> - void rehash_impl(size_type count) { - sparse_hash new_table(count, static_cast(*this), - static_cast(*this), - static_cast(*this), m_max_load_factor); - - for (const auto &bucket : m_sparse_buckets_data) { - for (const auto &val : bucket) { - new_table.insert_on_rehash(val); - } - } - - new_table.swap(*this); - } - - template - void insert_on_rehash(K &&key_value) { - const std::size_t hash = hash_key(KeySelect()(key_value)); - std::size_t ibucket = bucket_for_hash(hash); - - std::size_t probe = 0; - while (true) { - std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); - - if (!m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - m_sparse_buckets[sparse_ibucket].set(*this, index_in_sparse_bucket, - std::forward(key_value)); - m_nb_elements++; - - return; - } else { - tsl_sh_assert( - !compare_keys(KeySelect()(key_value), - KeySelect()(*m_sparse_buckets[sparse_ibucket].value( - index_in_sparse_bucket)))); - } - - probe++; - ibucket = next_bucket(ibucket, probe); - } - } - - template - void serialize_impl(Serializer &serializer) const { - const slz_size_type version = SERIALIZATION_PROTOCOL_VERSION; - serializer(version); - - const slz_size_type bucket_count = m_bucket_count; - serializer(bucket_count); - - const slz_size_type nb_sparse_buckets = m_sparse_buckets_data.size(); - serializer(nb_sparse_buckets); - - const slz_size_type nb_elements = m_nb_elements; - serializer(nb_elements); - - const slz_size_type nb_deleted_buckets = m_nb_deleted_buckets; - serializer(nb_deleted_buckets); - - const float max_load_factor = m_max_load_factor; - serializer(max_load_factor); - - for (const auto &bucket : m_sparse_buckets_data) { - bucket.serialize(serializer); - } - } - - template - void deserialize_impl(Deserializer &deserializer, bool hash_compatible) { - tsl_sh_assert( - m_bucket_count == 0 && - m_sparse_buckets_data.empty()); // Current hash table must be empty - - const slz_size_type version = - deserialize_value(deserializer); - // For now we only have one version of the serialization protocol. - // If it doesn't match there is a problem with the file. - if (version != SERIALIZATION_PROTOCOL_VERSION) { - TSL_SH_THROW_OR_ABORT(std::runtime_error, - "Can't deserialize the sparse_map/set. The " - "protocol version header is invalid."); - } - - const slz_size_type bucket_count_ds = - deserialize_value(deserializer); - const slz_size_type nb_sparse_buckets = - deserialize_value(deserializer); - const slz_size_type nb_elements = - deserialize_value(deserializer); - const slz_size_type nb_deleted_buckets = - deserialize_value(deserializer); - const float max_load_factor = deserialize_value(deserializer); - - if (!hash_compatible) { - this->max_load_factor(max_load_factor); - reserve(numeric_cast(nb_elements, - "Deserialized nb_elements is too big.")); - for (slz_size_type ibucket = 0; ibucket < nb_sparse_buckets; ibucket++) { - sparse_array::deserialize_values_into_sparse_hash(deserializer, *this); - } - } else { - m_bucket_count = numeric_cast( - bucket_count_ds, "Deserialized bucket_count is too big."); - - GrowthPolicy::operator=(GrowthPolicy(m_bucket_count)); - // GrowthPolicy should not modify the bucket count we got from - // deserialization - if (m_bucket_count != bucket_count_ds) { - TSL_SH_THROW_OR_ABORT(std::runtime_error, - "The GrowthPolicy is not the same even though " - "hash_compatible is true."); - } - - if (nb_sparse_buckets != - sparse_array::nb_sparse_buckets(m_bucket_count)) { - TSL_SH_THROW_OR_ABORT(std::runtime_error, - "Deserialized nb_sparse_buckets is invalid."); - } - - m_nb_elements = numeric_cast( - nb_elements, "Deserialized nb_elements is too big."); - m_nb_deleted_buckets = numeric_cast( - nb_deleted_buckets, "Deserialized nb_deleted_buckets is too big."); - - m_sparse_buckets_data.reserve(numeric_cast( - nb_sparse_buckets, "Deserialized nb_sparse_buckets is too big.")); - for (slz_size_type ibucket = 0; ibucket < nb_sparse_buckets; ibucket++) { - m_sparse_buckets_data.emplace_back( - sparse_array::deserialize_hash_compatible( - deserializer, static_cast(*this))); - } - - if (!m_sparse_buckets_data.empty()) { - m_sparse_buckets_data.back().set_as_last(); - m_sparse_buckets = m_sparse_buckets_data.data(); - } - - this->max_load_factor(max_load_factor); - if (load_factor() > this->max_load_factor()) { - TSL_SH_THROW_OR_ABORT( - std::runtime_error, - "Invalid max_load_factor. Check that the serializer and " - "deserializer support " - "floats correctly as they can be converted implicitely to ints."); - } - } - } - - public: - static const size_type DEFAULT_INIT_BUCKET_COUNT = 0; - static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f; - - /** - * Protocol version currenlty used for serialization. - */ - static const slz_size_type SERIALIZATION_PROTOCOL_VERSION = 1; - - /** - * Return an always valid pointer to an static empty bucket_entry with - * last_bucket() == true. - */ - sparse_array *static_empty_sparse_bucket_ptr() { - static sparse_array empty_sparse_bucket(true); - return &empty_sparse_bucket; - } - - private: - sparse_buckets_container m_sparse_buckets_data; - - /** - * Points to m_sparse_buckets_data.data() if !m_sparse_buckets_data.empty() - * otherwise points to static_empty_sparse_bucket_ptr. This variable is useful - * to avoid the cost of checking if m_sparse_buckets_data is empty when trying - * to find an element. - * - * TODO Remove m_sparse_buckets_data and only use a pointer instead of a - * pointer+vector to save some space in the sparse_hash object. - */ - sparse_array *m_sparse_buckets; - - size_type m_bucket_count; - size_type m_nb_elements; - size_type m_nb_deleted_buckets; - - /** - * Maximum that m_nb_elements can reach before a rehash occurs automatically - * to grow the hash table. - */ - size_type m_load_threshold_rehash; - - /** - * Maximum that m_nb_elements + m_nb_deleted_buckets can reach before cleaning - * up the buckets marked as deleted. - */ - size_type m_load_threshold_clear_deleted; - float m_max_load_factor; -}; - -} // namespace detail_sparse_hash -} // namespace tsl + static const std::size_t BUCKET_MASK = BITMAP_NB_BITS - 1; + + static_assert( + is_power_of_two(BITMAP_NB_BITS), "BITMAP_NB_BITS must be a power of two."); + static_assert(std::numeric_limits::digits >= BITMAP_NB_BITS, + "bitmap_type must be able to hold at least BITMAP_NB_BITS."); + static_assert((std::size_t(1) << BUCKET_SHIFT) == BITMAP_NB_BITS, + "(1 << BUCKET_SHIFT) must be equal to BITMAP_NB_BITS."); + static_assert(std::numeric_limits::max() >= BITMAP_NB_BITS, + "size_type must be big enough to hold BITMAP_NB_BITS."); + static_assert(std::is_unsigned::value, "bitmap_type must be unsigned."); + static_assert( + (std::numeric_limits::max() & BUCKET_MASK) == BITMAP_NB_BITS - 1, ""); + + public: + /** + * Map an ibucket [0, bucket_count) in the hash table to a sparse_ibucket + * (a sparse_array holds multiple buckets, so there is less sparse_array than + * bucket_count). + * + * The bucket ibucket is in + * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] + * instead of something like m_buckets[ibucket] in a classical hash table. + */ + static std::size_t sparse_ibucket(std::size_t ibucket) + { + return ibucket >> BUCKET_SHIFT; + } + + /** + * Map an ibucket [0, bucket_count) in the hash table to an index in the + * sparse_array which corresponds to the bucket. + * + * The bucket ibucket is in + * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] + * instead of something like m_buckets[ibucket] in a classical hash table. + */ + static typename sparse_array::size_type index_in_sparse_bucket(std::size_t ibucket) + { + return static_cast( + ibucket & sparse_array::BUCKET_MASK); + } + + static std::size_t nb_sparse_buckets(std::size_t bucket_count) noexcept + { + if (bucket_count == 0) + { + return 0; + } + + return std::max( + 1, sparse_ibucket( + tsl::detail_sparse_hash::round_up_to_power_of_two(bucket_count))); + } + + public: + sparse_array() noexcept + : m_values(nullptr) + , m_bitmap_vals(0) + , m_bitmap_deleted_vals(0) + , m_nb_elements(0) + , m_capacity(0) + , m_last_array(false) + {} + + explicit sparse_array(bool last_bucket) noexcept + : m_values(nullptr) + , m_bitmap_vals(0) + , m_bitmap_deleted_vals(0) + , m_nb_elements(0) + , m_capacity(0) + , m_last_array(last_bucket) + {} + + sparse_array(size_type capacity, Allocator& alloc) + : m_values(nullptr) + , m_bitmap_vals(0) + , m_bitmap_deleted_vals(0) + , m_nb_elements(0) + , m_capacity(capacity) + , m_last_array(false) + { + if (m_capacity > 0) + { + m_values = alloc.allocate(m_capacity); + tsl_sh_assert( + m_values != nullptr); // allocate should throw if there is a failure + } + } + + sparse_array(const sparse_array& other, Allocator& alloc) + : m_values(nullptr) + , m_bitmap_vals(other.m_bitmap_vals) + , m_bitmap_deleted_vals(other.m_bitmap_deleted_vals) + , m_nb_elements(0) + , m_capacity(other.m_capacity) + , m_last_array(other.m_last_array) + { + tsl_sh_assert(other.m_capacity >= other.m_nb_elements); + if (m_capacity == 0) + { + return; + } + + m_values = alloc.allocate(m_capacity); + tsl_sh_assert( + m_values != nullptr); // allocate should throw if there is a failure + TSL_SH_TRY + { + for (size_type i = 0; i < other.m_nb_elements; i++) + { + construct_value(alloc, m_values + i, other.m_values[i]); + m_nb_elements++; + } + } + TSL_SH_CATCH(...) + { + clear(alloc); + TSL_SH_RETRHOW; + } + } + + sparse_array(sparse_array&& other) noexcept + : m_values(other.m_values) + , m_bitmap_vals(other.m_bitmap_vals) + , m_bitmap_deleted_vals(other.m_bitmap_deleted_vals) + , m_nb_elements(other.m_nb_elements) + , m_capacity(other.m_capacity) + , m_last_array(other.m_last_array) + { + other.m_values = nullptr; + other.m_bitmap_vals = 0; + other.m_bitmap_deleted_vals = 0; + other.m_nb_elements = 0; + other.m_capacity = 0; + } + + sparse_array(sparse_array&& other, Allocator& alloc) + : m_values(nullptr) + , m_bitmap_vals(other.m_bitmap_vals) + , m_bitmap_deleted_vals(other.m_bitmap_deleted_vals) + , m_nb_elements(0) + , m_capacity(other.m_capacity) + , m_last_array(other.m_last_array) + { + tsl_sh_assert(other.m_capacity >= other.m_nb_elements); + if (m_capacity == 0) + { + return; + } + + m_values = alloc.allocate(m_capacity); + tsl_sh_assert( + m_values != nullptr); // allocate should throw if there is a failure + TSL_SH_TRY + { + for (size_type i = 0; i < other.m_nb_elements; i++) + { + construct_value(alloc, m_values + i, std::move(other.m_values[i])); + m_nb_elements++; + } + } + TSL_SH_CATCH(...) + { + clear(alloc); + TSL_SH_RETRHOW; + } + } + + sparse_array& operator=(const sparse_array&) = delete; + sparse_array& operator=(sparse_array&&) = delete; + + ~sparse_array() noexcept + { + // The code that manages the sparse_array must have called clear before + // destruction. See documentation of sparse_array for more details. + tsl_sh_assert(m_capacity == 0 && m_nb_elements == 0 && m_values == nullptr); + } + + iterator begin() noexcept + { + return m_values; + } + iterator end() noexcept + { + return m_values + m_nb_elements; + } + const_iterator begin() const noexcept + { + return cbegin(); + } + const_iterator end() const noexcept + { + return cend(); + } + const_iterator cbegin() const noexcept + { + return m_values; + } + const_iterator cend() const noexcept + { + return m_values + m_nb_elements; + } + + bool empty() const noexcept + { + return m_nb_elements == 0; + } + + size_type size() const noexcept + { + return m_nb_elements; + } + + void clear(allocator_type& alloc) noexcept + { + destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + + m_values = nullptr; + m_bitmap_vals = 0; + m_bitmap_deleted_vals = 0; + m_nb_elements = 0; + m_capacity = 0; + } + + bool last() const noexcept + { + return m_last_array; + } + + void set_as_last() noexcept + { + m_last_array = true; + } + + bool has_value(size_type index) const noexcept + { + tsl_sh_assert(index < BITMAP_NB_BITS); + return (m_bitmap_vals & (bitmap_type(1) << index)) != 0; + } + + bool has_deleted_value(size_type index) const noexcept + { + tsl_sh_assert(index < BITMAP_NB_BITS); + return (m_bitmap_deleted_vals & (bitmap_type(1) << index)) != 0; + } + + iterator value(size_type index) noexcept + { + tsl_sh_assert(has_value(index)); + return m_values + index_to_offset(index); + } + + const_iterator value(size_type index) const noexcept + { + tsl_sh_assert(has_value(index)); + return m_values + index_to_offset(index); + } + + /** + * Return iterator to set value. + */ + template + iterator set(allocator_type& alloc, size_type index, Args&&... value_args) + { + tsl_sh_assert(!has_value(index)); + + const size_type offset = index_to_offset(index); + insert_at_offset(alloc, offset, std::forward(value_args)...); + + m_bitmap_vals = (m_bitmap_vals | (bitmap_type(1) << index)); + m_bitmap_deleted_vals = (m_bitmap_deleted_vals & ~(bitmap_type(1) << index)); + + m_nb_elements++; + + tsl_sh_assert(has_value(index)); + tsl_sh_assert(!has_deleted_value(index)); + + return m_values + offset; + } + + iterator erase(allocator_type& alloc, iterator position) + { + const size_type offset = static_cast(std::distance(begin(), position)); + return erase(alloc, position, offset_to_index(offset)); + } + + // Return the next value or end if no next value + iterator erase(allocator_type& alloc, iterator position, size_type index) + { + tsl_sh_assert(has_value(index)); + tsl_sh_assert(!has_deleted_value(index)); + + const size_type offset = static_cast(std::distance(begin(), position)); + erase_at_offset(alloc, offset); + + m_bitmap_vals = (m_bitmap_vals & ~(bitmap_type(1) << index)); + m_bitmap_deleted_vals = (m_bitmap_deleted_vals | (bitmap_type(1) << index)); + + m_nb_elements--; + + tsl_sh_assert(!has_value(index)); + tsl_sh_assert(has_deleted_value(index)); + + return m_values + offset; + } + + void swap(sparse_array& other) + { + using std::swap; + + swap(m_values, other.m_values); + swap(m_bitmap_vals, other.m_bitmap_vals); + swap(m_bitmap_deleted_vals, other.m_bitmap_deleted_vals); + swap(m_nb_elements, other.m_nb_elements); + swap(m_capacity, other.m_capacity); + swap(m_last_array, other.m_last_array); + } + + static iterator mutable_iterator(const_iterator pos) + { + return const_cast(pos); + } + + template + void serialize(Serializer& serializer) const + { + const slz_size_type sparse_bucket_size = m_nb_elements; + serializer(sparse_bucket_size); + + const slz_size_type bitmap_vals = m_bitmap_vals; + serializer(bitmap_vals); + + const slz_size_type bitmap_deleted_vals = m_bitmap_deleted_vals; + serializer(bitmap_deleted_vals); + + for (const value_type& value : *this) + { + serializer(value); + } + } + + template + static sparse_array deserialize_hash_compatible( + Deserializer& deserializer, Allocator& alloc) + { + const slz_size_type sparse_bucket_size = + deserialize_value(deserializer); + const slz_size_type bitmap_vals = deserialize_value(deserializer); + const slz_size_type bitmap_deleted_vals = + deserialize_value(deserializer); + + if (sparse_bucket_size > BITMAP_NB_BITS) + { + TSL_SH_THROW_OR_ABORT(std::runtime_error, + "Deserialized sparse_bucket_size is too big for the platform. " + "Maximum should be BITMAP_NB_BITS."); + } + + sparse_array sarray; + if (sparse_bucket_size == 0) + { + return sarray; + } + + sarray.m_bitmap_vals = + numeric_cast(bitmap_vals, "Deserialized bitmap_vals is too big."); + sarray.m_bitmap_deleted_vals = numeric_cast( + bitmap_deleted_vals, "Deserialized bitmap_deleted_vals is too big."); + + sarray.m_capacity = numeric_cast( + sparse_bucket_size, "Deserialized sparse_bucket_size is too big."); + sarray.m_values = alloc.allocate(sarray.m_capacity); + + TSL_SH_TRY + { + for (size_type ivalue = 0; ivalue < sarray.m_capacity; ivalue++) + { + construct_value(alloc, sarray.m_values + ivalue, + deserialize_value(deserializer)); + sarray.m_nb_elements++; + } + } + TSL_SH_CATCH(...) + { + sarray.clear(alloc); + TSL_SH_RETRHOW; + } + + return sarray; + } + + /** + * Deserialize the values of the bucket and insert them all in sparse_hash + * through sparse_hash.insert(...). + */ + template + static void deserialize_values_into_sparse_hash( + Deserializer& deserializer, SparseHash& sparse_hash) + { + const slz_size_type sparse_bucket_size = + deserialize_value(deserializer); + + const slz_size_type bitmap_vals = deserialize_value(deserializer); + static_cast(bitmap_vals); // Ignore, not needed + + const slz_size_type bitmap_deleted_vals = + deserialize_value(deserializer); + static_cast(bitmap_deleted_vals); // Ignore, not needed + + for (slz_size_type ivalue = 0; ivalue < sparse_bucket_size; ivalue++) + { + sparse_hash.insert(deserialize_value(deserializer)); + } + } + + private: + template + static void construct_value( + allocator_type& alloc, value_type* value, Args&&... value_args) + { + std::allocator_traits::construct( + alloc, value, std::forward(value_args)...); + } + + static void destroy_value(allocator_type& alloc, value_type* value) noexcept + { + std::allocator_traits::destroy(alloc, value); + } + + static void destroy_and_deallocate_values(allocator_type& alloc, value_type* values, + size_type nb_values, size_type capacity_values) noexcept + { + for (size_type i = 0; i < nb_values; i++) + { + destroy_value(alloc, values + i); + } + + alloc.deallocate(values, capacity_values); + } + + static size_type popcount(bitmap_type val) noexcept + { + if (sizeof(bitmap_type) <= sizeof(unsigned int)) + { + return static_cast( + tsl::detail_popcount::popcount(static_cast(val))); + } + else + { + return static_cast(tsl::detail_popcount::popcountll(val)); + } + } + + size_type index_to_offset(size_type index) const noexcept + { + tsl_sh_assert(index < BITMAP_NB_BITS); + return popcount(m_bitmap_vals & ((bitmap_type(1) << index) - bitmap_type(1))); + } + + // TODO optimize + size_type offset_to_index(size_type offset) const noexcept + { + tsl_sh_assert(offset < m_nb_elements); + + bitmap_type bitmap_vals = m_bitmap_vals; + size_type index = 0; + size_type nb_ones = 0; + + while (bitmap_vals != 0) + { + if ((bitmap_vals & 0x1) == 1) + { + if (nb_ones == offset) + { + break; + } + + nb_ones++; + } + + index++; + bitmap_vals = bitmap_vals >> 1; + } + + return index; + } + + size_type next_capacity() const noexcept + { + return static_cast(m_capacity + CAPACITY_GROWTH_STEP); + } + + /** + * Insertion + * + * Two situations: + * - Either we are in a situation where + * std::is_nothrow_move_constructible::value is true. In this + * case, on insertion we just reallocate m_values when we reach its capacity + * (i.e. m_nb_elements == m_capacity), otherwise we just put the new value at + * its appropriate place. We can easily keep the strong exception guarantee as + * moving the values around is safe. + * - Otherwise we are in a situation where + * std::is_nothrow_move_constructible::value is false. In this + * case on EACH insertion we allocate a new area of m_nb_elements + 1 where we + * copy the values of m_values into it and put the new value there. On + * success, we set m_values to this new area. Even if slower, it's the only + * way to preserve to strong exception guarantee. + */ + template::value>::type* = + nullptr> + void insert_at_offset(allocator_type& alloc, size_type offset, Args&&... value_args) + { + if (m_nb_elements < m_capacity) + { + insert_at_offset_no_realloc(alloc, offset, std::forward(value_args)...); + } + else + { + insert_at_offset_realloc( + alloc, offset, next_capacity(), std::forward(value_args)...); + } + } + + template::value>::type* = + nullptr> + void insert_at_offset(allocator_type& alloc, size_type offset, Args&&... value_args) + { + insert_at_offset_realloc( + alloc, offset, m_nb_elements + 1, std::forward(value_args)...); + } + + template::value>::type* = + nullptr> + void insert_at_offset_no_realloc( + allocator_type& alloc, size_type offset, Args&&... value_args) + { + tsl_sh_assert(offset <= m_nb_elements); + tsl_sh_assert(m_nb_elements < m_capacity); + + for (size_type i = m_nb_elements; i > offset; i--) + { + construct_value(alloc, m_values + i, std::move(m_values[i - 1])); + destroy_value(alloc, m_values + i - 1); + } + + TSL_SH_TRY + { + construct_value(alloc, m_values + offset, std::forward(value_args)...); + } + TSL_SH_CATCH(...) + { + for (size_type i = offset; i < m_nb_elements; i++) + { + construct_value(alloc, m_values + i, std::move(m_values[i + 1])); + destroy_value(alloc, m_values + i + 1); + } + TSL_SH_RETRHOW; + } + } + + template::value>::type* = + nullptr> + void insert_at_offset_realloc(allocator_type& alloc, size_type offset, + size_type new_capacity, Args&&... value_args) + { + tsl_sh_assert(new_capacity > m_nb_elements); + + value_type* new_values = alloc.allocate(new_capacity); + // Allocate should throw if there is a failure + tsl_sh_assert(new_values != nullptr); + + TSL_SH_TRY + { + construct_value(alloc, new_values + offset, std::forward(value_args)...); + } + TSL_SH_CATCH(...) + { + alloc.deallocate(new_values, new_capacity); + TSL_SH_RETRHOW; + } + + // Should not throw from here + for (size_type i = 0; i < offset; i++) + { + construct_value(alloc, new_values + i, std::move(m_values[i])); + } + + for (size_type i = offset; i < m_nb_elements; i++) + { + construct_value(alloc, new_values + i + 1, std::move(m_values[i])); + } + + destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + + m_values = new_values; + m_capacity = new_capacity; + } + + template::value>::type* = + nullptr> + void insert_at_offset_realloc(allocator_type& alloc, size_type offset, + size_type new_capacity, Args&&... value_args) + { + tsl_sh_assert(new_capacity > m_nb_elements); + + value_type* new_values = alloc.allocate(new_capacity); + // Allocate should throw if there is a failure + tsl_sh_assert(new_values != nullptr); + + size_type nb_new_values = 0; + TSL_SH_TRY + { + for (size_type i = 0; i < offset; i++) + { + construct_value(alloc, new_values + i, m_values[i]); + nb_new_values++; + } + + construct_value(alloc, new_values + offset, std::forward(value_args)...); + nb_new_values++; + + for (size_type i = offset; i < m_nb_elements; i++) + { + construct_value(alloc, new_values + i + 1, m_values[i]); + nb_new_values++; + } + } + TSL_SH_CATCH(...) + { + destroy_and_deallocate_values(alloc, new_values, nb_new_values, new_capacity); + TSL_SH_RETRHOW; + } + + tsl_sh_assert(nb_new_values == m_nb_elements + 1); + + destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + + m_values = new_values; + m_capacity = new_capacity; + } + + /** + * Erasure + * + * Two situations: + * - Either we are in a situation where + * std::is_nothrow_move_constructible::value is true. Simply + * destroy the value and left-shift move the value on the right of offset. + * - Otherwise we are in a situation where + * std::is_nothrow_move_constructible::value is false. Copy all + * the values except the one at offset into a new heap area. On success, we + * set m_values to this new area. Even if slower, it's the only way to + * preserve to strong exception guarantee. + */ + template::value>::type* = + nullptr> + void erase_at_offset(allocator_type& alloc, size_type offset) noexcept + { + tsl_sh_assert(offset < m_nb_elements); + + destroy_value(alloc, m_values + offset); + + for (size_type i = offset + 1; i < m_nb_elements; i++) + { + construct_value(alloc, m_values + i - 1, std::move(m_values[i])); + destroy_value(alloc, m_values + i); + } + } + + template::value>::type* = + nullptr> + void erase_at_offset(allocator_type& alloc, size_type offset) + { + tsl_sh_assert(offset < m_nb_elements); + + // Erasing the last element, don't need to reallocate. We keep the capacity. + if (offset + 1 == m_nb_elements) + { + destroy_value(alloc, m_values + offset); + return; + } + + tsl_sh_assert(m_nb_elements > 1); + const size_type new_capacity = m_nb_elements - 1; + + value_type* new_values = alloc.allocate(new_capacity); + // Allocate should throw if there is a failure + tsl_sh_assert(new_values != nullptr); + + size_type nb_new_values = 0; + TSL_SH_TRY + { + for (size_type i = 0; i < m_nb_elements; i++) + { + if (i != offset) + { + construct_value(alloc, new_values + nb_new_values, m_values[i]); + nb_new_values++; + } + } + } + TSL_SH_CATCH(...) + { + destroy_and_deallocate_values(alloc, new_values, nb_new_values, new_capacity); + TSL_SH_RETRHOW; + } + + tsl_sh_assert(nb_new_values == m_nb_elements - 1); + + destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + + m_values = new_values; + m_capacity = new_capacity; + } + + private: + value_type* m_values; + + bitmap_type m_bitmap_vals; + bitmap_type m_bitmap_deleted_vals; + + size_type m_nb_elements; + size_type m_capacity; + bool m_last_array; + }; + + /** + * Internal common class used by `sparse_map` and `sparse_set`. + * + * `ValueType` is what will be stored by `sparse_hash` (usually `std::pair` for map and `Key` for set). + * + * `KeySelect` should be a `FunctionObject` which takes a `ValueType` in + * parameter and returns a reference to the key. + * + * `ValueSelect` should be a `FunctionObject` which takes a `ValueType` in + * parameter and returns a reference to the value. `ValueSelect` should be void + * if there is no value (in a set for example). + * + * The strong exception guarantee only holds if `ExceptionSafety` is set to + * `tsl::sh::exception_safety::strong`. + * + * `ValueType` must be nothrow move constructible and/or copy constructible. + * Behaviour is undefined if the destructor of `ValueType` throws. + * + * + * The class holds its buckets in a 2-dimensional fashion. Instead of having a + * linear `std::vector` for [0, bucket_count) where each bucket stores + * one value, we have a `std::vector` (m_sparse_buckets_data) + * where each `sparse_array` stores multiple values (up to + * `sparse_array::BITMAP_NB_BITS`). To convert a one dimensional `ibucket` + * position to a position in `std::vector` and a position in + * `sparse_array`, use respectively the methods + * `sparse_array::sparse_ibucket(ibucket)` and + * `sparse_array::index_in_sparse_bucket(ibucket)`. + */ + template + class sparse_hash : private Allocator, + private Hash, + private KeyEqual, + private GrowthPolicy + { + private: + template + using has_mapped_type = + typename std::integral_constant::value>; + + static_assert(noexcept(std::declval().bucket_for_hash(std::size_t(0))), + "GrowthPolicy::bucket_for_hash must be noexcept."); + static_assert(noexcept(std::declval().clear()), + "GrowthPolicy::clear must be noexcept."); + + public: + template + class sparse_iterator; + + using key_type = typename KeySelect::key_type; + using value_type = ValueType; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using hasher = Hash; + using key_equal = KeyEqual; + using allocator_type = Allocator; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = sparse_iterator; + using const_iterator = sparse_iterator; + + private: + using sparse_array = + tsl::detail_sparse_hash::sparse_array; + + using sparse_buckets_allocator = + typename std::allocator_traits::template rebind_alloc; + using sparse_buckets_container = std::vector; + + public: + /** + * The `operator*()` and `operator->()` methods return a const reference and + * const pointer respectively to the stored value type (`Key` for a set, + * `std::pair` for a map). + * + * In case of a map, to get a mutable reference to the value `T` associated to + * a key (the `.second` in the stored pair), you have to call `value()`. + */ + template + class sparse_iterator + { + friend class sparse_hash; + + private: + using sparse_bucket_iterator = typename std::conditional::type; + + using sparse_array_iterator = typename std::conditional::type; + + /** + * sparse_array_it should be nullptr if sparse_bucket_it == + * m_sparse_buckets_data.end(). (TODO better way?) + */ + sparse_iterator( + sparse_bucket_iterator sparse_bucket_it, sparse_array_iterator sparse_array_it) + : m_sparse_buckets_it(sparse_bucket_it), m_sparse_array_it(sparse_array_it) + {} + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = const typename sparse_hash::value_type; + using difference_type = std::ptrdiff_t; + using reference = value_type&; + using pointer = value_type*; + + sparse_iterator() noexcept {} + + // Copy constructor from iterator to const_iterator. + template::type* = nullptr> + sparse_iterator(const sparse_iterator& other) noexcept + : m_sparse_buckets_it(other.m_sparse_buckets_it) + , m_sparse_array_it(other.m_sparse_array_it) + {} + + sparse_iterator(const sparse_iterator& other) = default; + sparse_iterator(sparse_iterator&& other) = default; + sparse_iterator& operator=(const sparse_iterator& other) = default; + sparse_iterator& operator=(sparse_iterator&& other) = default; + + const typename sparse_hash::key_type& key() const + { + return KeySelect()(*m_sparse_array_it); + } + + template::value && IsConst>::type* = nullptr> + const typename U::value_type& value() const + { + return U()(*m_sparse_array_it); + } + + template::value && !IsConst>::type* = nullptr> + typename U::value_type& value() + { + return U()(*m_sparse_array_it); + } + + reference operator*() const + { + return *m_sparse_array_it; + } + + pointer operator->() const + { + return std::addressof(*m_sparse_array_it); + } + + sparse_iterator& operator++() + { + tsl_sh_assert(m_sparse_array_it != nullptr); + ++m_sparse_array_it; + + if (m_sparse_array_it == m_sparse_buckets_it->end()) + { + do + { + if (m_sparse_buckets_it->last()) + { + ++m_sparse_buckets_it; + m_sparse_array_it = nullptr; + return *this; + } + + ++m_sparse_buckets_it; + } while (m_sparse_buckets_it->empty()); + + m_sparse_array_it = m_sparse_buckets_it->begin(); + } + + return *this; + } + + sparse_iterator operator++(int) + { + sparse_iterator tmp(*this); + ++*this; + + return tmp; + } + + friend bool operator==(const sparse_iterator& lhs, const sparse_iterator& rhs) + { + return lhs.m_sparse_buckets_it == rhs.m_sparse_buckets_it + && lhs.m_sparse_array_it == rhs.m_sparse_array_it; + } + + friend bool operator!=(const sparse_iterator& lhs, const sparse_iterator& rhs) + { + return !(lhs == rhs); + } + + private: + sparse_bucket_iterator m_sparse_buckets_it; + sparse_array_iterator m_sparse_array_it; + }; + + public: + sparse_hash(size_type bucket_count, const Hash& hash, const KeyEqual& equal, + const Allocator& alloc, float max_load_factor) + : Allocator(alloc) + , Hash(hash) + , KeyEqual(equal) + , GrowthPolicy(bucket_count) + , m_sparse_buckets_data(alloc) + , m_sparse_buckets(static_empty_sparse_bucket_ptr()) + , m_bucket_count(bucket_count) + , m_nb_elements(0) + , m_nb_deleted_buckets(0) + { + if (m_bucket_count > max_bucket_count()) + { + TSL_SH_THROW_OR_ABORT(std::length_error, "The map exceeds its maximum size."); + } + + if (m_bucket_count > 0) + { + /* + * We can't use the `vector(size_type count, const Allocator& alloc)` + * constructor as it's only available in C++14 and we need to support + * C++11. We thus must resize after using the `vector(const Allocator& + * alloc)` constructor. + * + * We can't use `vector(size_type count, const T& value, const Allocator& + * alloc)` as it requires the value T to be copyable. + */ + m_sparse_buckets_data.resize(sparse_array::nb_sparse_buckets(bucket_count)); + m_sparse_buckets = m_sparse_buckets_data.data(); + + tsl_sh_assert(!m_sparse_buckets_data.empty()); + m_sparse_buckets_data.back().set_as_last(); + } + + this->max_load_factor(max_load_factor); + + // Check in the constructor instead of outside of a function to avoid + // compilation issues when value_type is not complete. + static_assert(std::is_nothrow_move_constructible::value + || std::is_copy_constructible::value, + "Key, and T if present, must be nothrow move constructible " + "and/or copy constructible."); + } + + ~sparse_hash() + { + clear(); + } + + sparse_hash(const sparse_hash& other) + : Allocator( + std::allocator_traits::select_on_container_copy_construction(other)) + , Hash(other) + , KeyEqual(other) + , GrowthPolicy(other) + , m_sparse_buckets_data( + std::allocator_traits::select_on_container_copy_construction( + other)) + , m_bucket_count(other.m_bucket_count) + , m_nb_elements(other.m_nb_elements) + , m_nb_deleted_buckets(other.m_nb_deleted_buckets) + , m_load_threshold_rehash(other.m_load_threshold_rehash) + , m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted) + , m_max_load_factor(other.m_max_load_factor) + { + copy_buckets_from(other), m_sparse_buckets = m_sparse_buckets_data.empty() + ? static_empty_sparse_bucket_ptr() + : m_sparse_buckets_data.data(); + } + + sparse_hash(sparse_hash&& other) noexcept( + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_constructible::value) + : Allocator(std::move(other)) + , Hash(std::move(other)) + , KeyEqual(std::move(other)) + , GrowthPolicy(std::move(other)) + , m_sparse_buckets_data(std::move(other.m_sparse_buckets_data)) + , m_sparse_buckets(m_sparse_buckets_data.empty() ? static_empty_sparse_bucket_ptr() + : m_sparse_buckets_data.data()) + , m_bucket_count(other.m_bucket_count) + , m_nb_elements(other.m_nb_elements) + , m_nb_deleted_buckets(other.m_nb_deleted_buckets) + , m_load_threshold_rehash(other.m_load_threshold_rehash) + , m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted) + , m_max_load_factor(other.m_max_load_factor) + { + other.GrowthPolicy::clear(); + other.m_sparse_buckets_data.clear(); + other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); + other.m_bucket_count = 0; + other.m_nb_elements = 0; + other.m_nb_deleted_buckets = 0; + other.m_load_threshold_rehash = 0; + other.m_load_threshold_clear_deleted = 0; + } + + sparse_hash& operator=(const sparse_hash& other) + { + if (this != &other) + { + clear(); + + if (std::allocator_traits< + Allocator>::propagate_on_container_copy_assignment::value) + { + Allocator::operator=(other); + } + + Hash::operator=(other); + KeyEqual::operator=(other); + GrowthPolicy::operator=(other); + + if (std::allocator_traits< + Allocator>::propagate_on_container_copy_assignment::value) + { + m_sparse_buckets_data = + sparse_buckets_container(static_cast(other)); + } + else + { + if (m_sparse_buckets_data.size() != other.m_sparse_buckets_data.size()) + { + m_sparse_buckets_data = + sparse_buckets_container(static_cast(*this)); + } + else + { + m_sparse_buckets_data.clear(); + } + } + + copy_buckets_from(other); + m_sparse_buckets = m_sparse_buckets_data.empty() + ? static_empty_sparse_bucket_ptr() + : m_sparse_buckets_data.data(); + + m_bucket_count = other.m_bucket_count; + m_nb_elements = other.m_nb_elements; + m_nb_deleted_buckets = other.m_nb_deleted_buckets; + m_load_threshold_rehash = other.m_load_threshold_rehash; + m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; + m_max_load_factor = other.m_max_load_factor; + } + + return *this; + } + + sparse_hash& operator=(sparse_hash&& other) + { + clear(); + + if (std::allocator_traits::propagate_on_container_move_assignment::value) + { + static_cast(*this) = std::move(static_cast(other)); + m_sparse_buckets_data = std::move(other.m_sparse_buckets_data); + } + else if (static_cast(*this) != static_cast(other)) + { + move_buckets_from(std::move(other)); + } + else + { + static_cast(*this) = std::move(static_cast(other)); + m_sparse_buckets_data = std::move(other.m_sparse_buckets_data); + } + + m_sparse_buckets = m_sparse_buckets_data.empty() ? static_empty_sparse_bucket_ptr() + : m_sparse_buckets_data.data(); + + static_cast(*this) = std::move(static_cast(other)); + static_cast(*this) = std::move(static_cast(other)); + static_cast(*this) = std::move(static_cast(other)); + m_bucket_count = other.m_bucket_count; + m_nb_elements = other.m_nb_elements; + m_nb_deleted_buckets = other.m_nb_deleted_buckets; + m_load_threshold_rehash = other.m_load_threshold_rehash; + m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; + m_max_load_factor = other.m_max_load_factor; + + other.GrowthPolicy::clear(); + other.m_sparse_buckets_data.clear(); + other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); + other.m_bucket_count = 0; + other.m_nb_elements = 0; + other.m_nb_deleted_buckets = 0; + other.m_load_threshold_rehash = 0; + other.m_load_threshold_clear_deleted = 0; + + return *this; + } + + allocator_type get_allocator() const + { + return static_cast(*this); + } + + /* + * Iterators + */ + iterator begin() noexcept + { + auto begin = m_sparse_buckets_data.begin(); + while (begin != m_sparse_buckets_data.end() && begin->empty()) + { + ++begin; + } + + return iterator( + begin, (begin != m_sparse_buckets_data.end()) ? begin->begin() : nullptr); + } + + const_iterator begin() const noexcept + { + return cbegin(); + } + + const_iterator cbegin() const noexcept + { + auto begin = m_sparse_buckets_data.cbegin(); + while (begin != m_sparse_buckets_data.cend() && begin->empty()) + { + ++begin; + } + + return const_iterator( + begin, (begin != m_sparse_buckets_data.cend()) ? begin->cbegin() : nullptr); + } + + iterator end() noexcept + { + return iterator(m_sparse_buckets_data.end(), nullptr); + } + + const_iterator end() const noexcept + { + return cend(); + } + + const_iterator cend() const noexcept + { + return const_iterator(m_sparse_buckets_data.cend(), nullptr); + } + + /* + * Capacity + */ + bool empty() const noexcept + { + return m_nb_elements == 0; + } + + size_type size() const noexcept + { + return m_nb_elements; + } + + size_type max_size() const noexcept + { + return std::min( + std::allocator_traits::max_size(), m_sparse_buckets_data.max_size()); + } + + /* + * Modifiers + */ + void clear() noexcept + { + for (auto& bucket : m_sparse_buckets_data) + { + bucket.clear(*this); + } + + m_nb_elements = 0; + m_nb_deleted_buckets = 0; + } + + template + std::pair insert(P&& value) + { + return insert_impl(KeySelect()(value), std::forward

(value)); + } + + template + iterator insert_hint(const_iterator hint, P&& value) + { + if (hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) + { + return mutable_iterator(hint); + } + + return insert(std::forward

(value)).first; + } + + template + void insert(InputIt first, InputIt last) + { + if (std::is_base_of::iterator_category>::value) + { + const auto nb_elements_insert = std::distance(first, last); + const size_type nb_free_buckets = m_load_threshold_rehash - size(); + tsl_sh_assert(m_load_threshold_rehash >= size()); + + if (nb_elements_insert > 0 && nb_free_buckets < size_type(nb_elements_insert)) + { + reserve(size() + size_type(nb_elements_insert)); + } + } + + for (; first != last; ++first) + { + insert(*first); + } + } + + template + std::pair insert_or_assign(K&& key, M&& obj) + { + auto it = try_emplace(std::forward(key), std::forward(obj)); + if (!it.second) + { + it.first.value() = std::forward(obj); + } + + return it; + } + + template + iterator insert_or_assign(const_iterator hint, K&& key, M&& obj) + { + if (hint != cend() && compare_keys(KeySelect()(*hint), key)) + { + auto it = mutable_iterator(hint); + it.value() = std::forward(obj); + + return it; + } + + return insert_or_assign(std::forward(key), std::forward(obj)).first; + } + + template + std::pair emplace(Args&&... args) + { + return insert(value_type(std::forward(args)...)); + } + + template + iterator emplace_hint(const_iterator hint, Args&&... args) + { + return insert_hint(hint, value_type(std::forward(args)...)); + } + + template + std::pair try_emplace(K&& key, Args&&... args) + { + return insert_impl(key, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); + } + + template + iterator try_emplace_hint(const_iterator hint, K&& key, Args&&... args) + { + if (hint != cend() && compare_keys(KeySelect()(*hint), key)) + { + return mutable_iterator(hint); + } + + return try_emplace(std::forward(key), std::forward(args)...).first; + } + + /** + * Here to avoid `template size_type erase(const K& key)` being used + * when we use an iterator instead of a const_iterator. + */ + iterator erase(iterator pos) + { + tsl_sh_assert(pos != end() && m_nb_elements > 0); + auto it_sparse_array_next = + pos.m_sparse_buckets_it->erase(*this, pos.m_sparse_array_it); + m_nb_elements--; + m_nb_deleted_buckets++; + + if (it_sparse_array_next == pos.m_sparse_buckets_it->end()) + { + auto it_sparse_buckets_next = pos.m_sparse_buckets_it; + do + { + ++it_sparse_buckets_next; + } while (it_sparse_buckets_next != m_sparse_buckets_data.end() + && it_sparse_buckets_next->empty()); + + if (it_sparse_buckets_next == m_sparse_buckets_data.end()) + { + return end(); + } + else + { + return iterator(it_sparse_buckets_next, it_sparse_buckets_next->begin()); + } + } + else + { + return iterator(pos.m_sparse_buckets_it, it_sparse_array_next); + } + } + + iterator erase(const_iterator pos) + { + return erase(mutable_iterator(pos)); + } + + iterator erase(const_iterator first, const_iterator last) + { + if (first == last) + { + return mutable_iterator(first); + } + + // TODO Optimize, could avoid the call to std::distance. + const size_type nb_elements_to_erase = + static_cast(std::distance(first, last)); + auto to_delete = mutable_iterator(first); + for (size_type i = 0; i < nb_elements_to_erase; i++) + { + to_delete = erase(to_delete); + } + + return to_delete; + } + + template + size_type erase(const K& key) + { + return erase(key, hash_key(key)); + } + + template + size_type erase(const K& key, std::size_t hash) + { + return erase_impl(key, hash); + } + + void swap(sparse_hash& other) + { + using std::swap; + + if (std::allocator_traits::propagate_on_container_swap::value) + { + swap(static_cast(*this), static_cast(other)); + } + else + { + tsl_sh_assert(static_cast(*this) == static_cast(other)); + } + + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), static_cast(other)); + swap(m_sparse_buckets_data, other.m_sparse_buckets_data); + swap(m_sparse_buckets, other.m_sparse_buckets); + swap(m_bucket_count, other.m_bucket_count); + swap(m_nb_elements, other.m_nb_elements); + swap(m_nb_deleted_buckets, other.m_nb_deleted_buckets); + swap(m_load_threshold_rehash, other.m_load_threshold_rehash); + swap(m_load_threshold_clear_deleted, other.m_load_threshold_clear_deleted); + swap(m_max_load_factor, other.m_max_load_factor); + } + + /* + * Lookup + */ + template::value>::type* = nullptr> + typename U::value_type& at(const K& key) + { + return at(key, hash_key(key)); + } + + template::value>::type* = nullptr> + typename U::value_type& at(const K& key, std::size_t hash) + { + return const_cast( + static_cast(this)->at(key, hash)); + } + + template::value>::type* = nullptr> + const typename U::value_type& at(const K& key) const + { + return at(key, hash_key(key)); + } + + template::value>::type* = nullptr> + const typename U::value_type& at(const K& key, std::size_t hash) const + { + auto it = find(key, hash); + if (it != cend()) + { + return it.value(); + } + else + { + TSL_SH_THROW_OR_ABORT(std::out_of_range, "Couldn't find key."); + } + } + + template::value>::type* = nullptr> + typename U::value_type& operator[](K&& key) + { + return try_emplace(std::forward(key)).first.value(); + } + + template + bool contains(const K& key) const + { + return contains(key, hash_key(key)); + } + + template + bool contains(const K& key, std::size_t hash) const + { + return count(key, hash) != 0; + } + + template + size_type count(const K& key) const + { + return count(key, hash_key(key)); + } + + template + size_type count(const K& key, std::size_t hash) const + { + if (find(key, hash) != cend()) + { + return 1; + } + else + { + return 0; + } + } + + template + iterator find(const K& key) + { + return find_impl(key, hash_key(key)); + } + + template + iterator find(const K& key, std::size_t hash) + { + return find_impl(key, hash); + } + + template + const_iterator find(const K& key) const + { + return find_impl(key, hash_key(key)); + } + + template + const_iterator find(const K& key, std::size_t hash) const + { + return find_impl(key, hash); + } + + template + std::pair equal_range(const K& key) + { + return equal_range(key, hash_key(key)); + } + + template + std::pair equal_range(const K& key, std::size_t hash) + { + iterator it = find(key, hash); + return std::make_pair(it, (it == end()) ? it : std::next(it)); + } + + template + std::pair equal_range(const K& key) const + { + return equal_range(key, hash_key(key)); + } + + template + std::pair equal_range( + const K& key, std::size_t hash) const + { + const_iterator it = find(key, hash); + return std::make_pair(it, (it == cend()) ? it : std::next(it)); + } + + /* + * Bucket interface + */ + size_type bucket_count() const + { + return m_bucket_count; + } + + size_type max_bucket_count() const + { + return m_sparse_buckets_data.max_size(); + } + + /* + * Hash policy + */ + float load_factor() const + { + if (bucket_count() == 0) + { + return 0; + } + + return float(m_nb_elements) / float(bucket_count()); + } + + float max_load_factor() const + { + return m_max_load_factor; + } + + void max_load_factor(float ml) + { + m_max_load_factor = std::max(0.1f, std::min(ml, 0.8f)); + m_load_threshold_rehash = size_type(float(bucket_count()) * m_max_load_factor); + + const float max_load_factor_with_deleted_buckets = + m_max_load_factor + 0.5f * (1.0f - m_max_load_factor); + tsl_sh_assert(max_load_factor_with_deleted_buckets > 0.0f + && max_load_factor_with_deleted_buckets <= 1.0f); + m_load_threshold_clear_deleted = + size_type(float(bucket_count()) * max_load_factor_with_deleted_buckets); + } + + void rehash(size_type count) + { + count = std::max(count, size_type(std::ceil(float(size()) / max_load_factor()))); + rehash_impl(count); + } + + void reserve(size_type count) + { + rehash(size_type(std::ceil(float(count) / max_load_factor()))); + } + + /* + * Observers + */ + hasher hash_function() const + { + return static_cast(*this); + } + + key_equal key_eq() const + { + return static_cast(*this); + } + + /* + * Other + */ + iterator mutable_iterator(const_iterator pos) + { + auto it_sparse_buckets = + m_sparse_buckets_data.begin() + + std::distance(m_sparse_buckets_data.cbegin(), pos.m_sparse_buckets_it); + + return iterator( + it_sparse_buckets, sparse_array::mutable_iterator(pos.m_sparse_array_it)); + } + + template + void serialize(Serializer& serializer) const + { + serialize_impl(serializer); + } + + template + void deserialize(Deserializer& deserializer, bool hash_compatible) + { + deserialize_impl(deserializer, hash_compatible); + } + + private: + template + std::size_t hash_key(const K& key) const + { + return Hash::operator()(key); + } + + template + bool compare_keys(const K1& key1, const K2& key2) const + { + return KeyEqual::operator()(key1, key2); + } + + size_type bucket_for_hash(std::size_t hash) const + { + const std::size_t bucket = GrowthPolicy::bucket_for_hash(hash); + tsl_sh_assert(sparse_array::sparse_ibucket(bucket) < m_sparse_buckets_data.size() + || (bucket == 0 && m_sparse_buckets_data.empty())); + + return bucket; + } + + template::value>::type* = nullptr> + size_type next_bucket(size_type ibucket, size_type iprobe) const + { + (void)iprobe; + if (Probing == tsl::sh::probing::linear) + { + return (ibucket + 1) & this->m_mask; + } + else + { + tsl_sh_assert(Probing == tsl::sh::probing::quadratic); + return (ibucket + iprobe) & this->m_mask; + } + } + + template::value>::type* = nullptr> + size_type next_bucket(size_type ibucket, size_type iprobe) const + { + (void)iprobe; + if (Probing == tsl::sh::probing::linear) + { + ibucket++; + return (ibucket != bucket_count()) ? ibucket : 0; + } + else + { + tsl_sh_assert(Probing == tsl::sh::probing::quadratic); + ibucket += iprobe; + return (ibucket < bucket_count()) ? ibucket : ibucket % bucket_count(); + } + } + + // TODO encapsulate m_sparse_buckets_data to avoid the managing the allocator + void copy_buckets_from(const sparse_hash& other) + { + m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); + + TSL_SH_TRY + { + for (const auto& bucket : other.m_sparse_buckets_data) + { + m_sparse_buckets_data.emplace_back(bucket, static_cast(*this)); + } + } + TSL_SH_CATCH(...) + { + clear(); + TSL_SH_RETRHOW; + } + + tsl_sh_assert(m_sparse_buckets_data.empty() || m_sparse_buckets_data.back().last()); + } + + void move_buckets_from(sparse_hash&& other) + { + m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); + + TSL_SH_TRY + { + for (auto&& bucket : other.m_sparse_buckets_data) + { + m_sparse_buckets_data.emplace_back( + std::move(bucket), static_cast(*this)); + } + } + TSL_SH_CATCH(...) + { + clear(); + TSL_SH_RETRHOW; + } + + tsl_sh_assert(m_sparse_buckets_data.empty() || m_sparse_buckets_data.back().last()); + } + + template + std::pair insert_impl(const K& key, Args&&... value_type_args) + { + /** + * We must insert the value in the first empty or deleted bucket we find. If + * we first find a deleted bucket, we still have to continue the search + * until we find an empty bucket or until we have searched all the buckets + * to be sure that the value is not in the hash table. We thus remember the + * position, if any, of the first deleted bucket we have encountered so we + * can insert it there if needed. + */ + bool found_first_deleted_bucket = false; + std::size_t sparse_ibucket_first_deleted = 0; + typename sparse_array::size_type index_in_sparse_bucket_first_deleted = 0; + + const std::size_t hash = hash_key(key); + std::size_t ibucket = bucket_for_hash(hash); + + std::size_t probe = 0; + while (true) + { + std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + auto index_in_sparse_bucket = sparse_array::index_in_sparse_bucket(ibucket); + + if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) + { + auto value_it = + m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); + if (compare_keys(key, KeySelect()(*value_it))) + { + return std::make_pair( + iterator(m_sparse_buckets_data.begin() + sparse_ibucket, value_it), + false); + } + } + else if (m_sparse_buckets[sparse_ibucket].has_deleted_value( + index_in_sparse_bucket) + && probe < m_bucket_count) + { + if (!found_first_deleted_bucket) + { + found_first_deleted_bucket = true; + sparse_ibucket_first_deleted = sparse_ibucket; + index_in_sparse_bucket_first_deleted = index_in_sparse_bucket; + } + } + else + { + /** + * At this point we are sure that the value does not exist + * in the hash table. + * First check if we satisfy load and delete thresholds, and if not, + * rehash the hash table (and therefore start over). Otherwise, just + * insert the value into the appropriate bucket. + */ + if (size() >= m_load_threshold_rehash) + { + rehash_impl(GrowthPolicy::next_bucket_count()); + return insert_impl(key, std::forward(value_type_args)...); + } + else if (size() + m_nb_deleted_buckets >= m_load_threshold_clear_deleted) + { + clear_deleted_buckets(); + return insert_impl(key, std::forward(value_type_args)...); + } + + if (found_first_deleted_bucket) + { + auto it = insert_in_bucket(sparse_ibucket_first_deleted, + index_in_sparse_bucket_first_deleted, + std::forward(value_type_args)...); + m_nb_deleted_buckets--; + + return it; + } + + return insert_in_bucket(sparse_ibucket, index_in_sparse_bucket, + std::forward(value_type_args)...); + } + + probe++; + ibucket = next_bucket(ibucket, probe); + } + } + + template + std::pair insert_in_bucket(std::size_t sparse_ibucket, + typename sparse_array::size_type index_in_sparse_bucket, Args&&... value_type_args) + { + auto value_it = m_sparse_buckets[sparse_ibucket].set( + *this, index_in_sparse_bucket, std::forward(value_type_args)...); + m_nb_elements++; + + return std::make_pair( + iterator(m_sparse_buckets_data.begin() + sparse_ibucket, value_it), true); + } + + template + size_type erase_impl(const K& key, std::size_t hash) + { + std::size_t ibucket = bucket_for_hash(hash); + + std::size_t probe = 0; + while (true) + { + const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + const auto index_in_sparse_bucket = + sparse_array::index_in_sparse_bucket(ibucket); + + if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) + { + auto value_it = + m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); + if (compare_keys(key, KeySelect()(*value_it))) + { + m_sparse_buckets[sparse_ibucket].erase( + *this, value_it, index_in_sparse_bucket); + m_nb_elements--; + m_nb_deleted_buckets++; + + return 1; + } + } + else if (!m_sparse_buckets[sparse_ibucket].has_deleted_value( + index_in_sparse_bucket) + || probe >= m_bucket_count) + { + return 0; + } + + probe++; + ibucket = next_bucket(ibucket, probe); + } + } + + template + iterator find_impl(const K& key, std::size_t hash) + { + return mutable_iterator(static_cast(this)->find(key, hash)); + } + + template + const_iterator find_impl(const K& key, std::size_t hash) const + { + std::size_t ibucket = bucket_for_hash(hash); + + std::size_t probe = 0; + while (true) + { + const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + const auto index_in_sparse_bucket = + sparse_array::index_in_sparse_bucket(ibucket); + + if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) + { + auto value_it = + m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); + if (compare_keys(key, KeySelect()(*value_it))) + { + return const_iterator( + m_sparse_buckets_data.cbegin() + sparse_ibucket, value_it); + } + } + else if (!m_sparse_buckets[sparse_ibucket].has_deleted_value( + index_in_sparse_bucket) + || probe >= m_bucket_count) + { + return cend(); + } + + probe++; + ibucket = next_bucket(ibucket, probe); + } + } + + void clear_deleted_buckets() + { + // TODO could be optimized, we could do it in-place instead of allocating a + // new bucket array. + rehash_impl(m_bucket_count); + tsl_sh_assert(m_nb_deleted_buckets == 0); + } + + template::type* = nullptr> + void rehash_impl(size_type count) + { + sparse_hash new_table(count, static_cast(*this), + static_cast(*this), static_cast(*this), + m_max_load_factor); + + for (auto& bucket : m_sparse_buckets_data) + { + for (auto& val : bucket) + { + new_table.insert_on_rehash(std::move(val)); + } + + // TODO try to reuse some of the memory + bucket.clear(*this); + } + + new_table.swap(*this); + } + + /** + * TODO: For now we copy each element into the new map. We could move + * them if they are nothrow_move_constructible without triggering + * any exception if we reserve enough space in the sparse arrays beforehand. + */ + template::type* = nullptr> + void rehash_impl(size_type count) + { + sparse_hash new_table(count, static_cast(*this), + static_cast(*this), static_cast(*this), + m_max_load_factor); + + for (const auto& bucket : m_sparse_buckets_data) + { + for (const auto& val : bucket) + { + new_table.insert_on_rehash(val); + } + } + + new_table.swap(*this); + } + + template + void insert_on_rehash(K&& key_value) + { + const std::size_t hash = hash_key(KeySelect()(key_value)); + std::size_t ibucket = bucket_for_hash(hash); + + std::size_t probe = 0; + while (true) + { + std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + auto index_in_sparse_bucket = sparse_array::index_in_sparse_bucket(ibucket); + + if (!m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) + { + m_sparse_buckets[sparse_ibucket].set( + *this, index_in_sparse_bucket, std::forward(key_value)); + m_nb_elements++; + + return; + } + else + { + tsl_sh_assert(!compare_keys(KeySelect()(key_value), + KeySelect()( + *m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket)))); + } + + probe++; + ibucket = next_bucket(ibucket, probe); + } + } + + template + void serialize_impl(Serializer& serializer) const + { + const slz_size_type version = SERIALIZATION_PROTOCOL_VERSION; + serializer(version); + + const slz_size_type bucket_count = m_bucket_count; + serializer(bucket_count); + + const slz_size_type nb_sparse_buckets = m_sparse_buckets_data.size(); + serializer(nb_sparse_buckets); + + const slz_size_type nb_elements = m_nb_elements; + serializer(nb_elements); + + const slz_size_type nb_deleted_buckets = m_nb_deleted_buckets; + serializer(nb_deleted_buckets); + + const float max_load_factor = m_max_load_factor; + serializer(max_load_factor); + + for (const auto& bucket : m_sparse_buckets_data) + { + bucket.serialize(serializer); + } + } + + template + void deserialize_impl(Deserializer& deserializer, bool hash_compatible) + { + tsl_sh_assert( + m_bucket_count == 0 + && m_sparse_buckets_data.empty()); // Current hash table must be empty + + const slz_size_type version = deserialize_value(deserializer); + // For now we only have one version of the serialization protocol. + // If it doesn't match there is a problem with the file. + if (version != SERIALIZATION_PROTOCOL_VERSION) + { + TSL_SH_THROW_OR_ABORT(std::runtime_error, + "Can't deserialize the sparse_map/set. The " + "protocol version header is invalid."); + } + + const slz_size_type bucket_count_ds = + deserialize_value(deserializer); + const slz_size_type nb_sparse_buckets = + deserialize_value(deserializer); + const slz_size_type nb_elements = deserialize_value(deserializer); + const slz_size_type nb_deleted_buckets = + deserialize_value(deserializer); + const float max_load_factor = deserialize_value(deserializer); + + if (!hash_compatible) + { + this->max_load_factor(max_load_factor); + reserve(numeric_cast( + nb_elements, "Deserialized nb_elements is too big.")); + for (slz_size_type ibucket = 0; ibucket < nb_sparse_buckets; ibucket++) + { + sparse_array::deserialize_values_into_sparse_hash(deserializer, *this); + } + } + else + { + m_bucket_count = numeric_cast( + bucket_count_ds, "Deserialized bucket_count is too big."); + + GrowthPolicy::operator=(GrowthPolicy(m_bucket_count)); + // GrowthPolicy should not modify the bucket count we got from + // deserialization + if (m_bucket_count != bucket_count_ds) + { + TSL_SH_THROW_OR_ABORT(std::runtime_error, + "The GrowthPolicy is not the same even though " + "hash_compatible is true."); + } + + if (nb_sparse_buckets != sparse_array::nb_sparse_buckets(m_bucket_count)) + { + TSL_SH_THROW_OR_ABORT( + std::runtime_error, "Deserialized nb_sparse_buckets is invalid."); + } + + m_nb_elements = numeric_cast( + nb_elements, "Deserialized nb_elements is too big."); + m_nb_deleted_buckets = numeric_cast( + nb_deleted_buckets, "Deserialized nb_deleted_buckets is too big."); + + m_sparse_buckets_data.reserve(numeric_cast( + nb_sparse_buckets, "Deserialized nb_sparse_buckets is too big.")); + for (slz_size_type ibucket = 0; ibucket < nb_sparse_buckets; ibucket++) + { + m_sparse_buckets_data.emplace_back( + sparse_array::deserialize_hash_compatible( + deserializer, static_cast(*this))); + } + + if (!m_sparse_buckets_data.empty()) + { + m_sparse_buckets_data.back().set_as_last(); + m_sparse_buckets = m_sparse_buckets_data.data(); + } + + this->max_load_factor(max_load_factor); + if (load_factor() > this->max_load_factor()) + { + TSL_SH_THROW_OR_ABORT(std::runtime_error, + "Invalid max_load_factor. Check that the serializer and " + "deserializer support " + "floats correctly as they can be converted implicitely to ints."); + } + } + } + + public: + static const size_type DEFAULT_INIT_BUCKET_COUNT = 0; + static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f; + + /** + * Protocol version currenlty used for serialization. + */ + static const slz_size_type SERIALIZATION_PROTOCOL_VERSION = 1; + + /** + * Return an always valid pointer to an static empty bucket_entry with + * last_bucket() == true. + */ + sparse_array* static_empty_sparse_bucket_ptr() + { + static sparse_array empty_sparse_bucket(true); + return &empty_sparse_bucket; + } + + private: + sparse_buckets_container m_sparse_buckets_data; + + /** + * Points to m_sparse_buckets_data.data() if !m_sparse_buckets_data.empty() + * otherwise points to static_empty_sparse_bucket_ptr. This variable is useful + * to avoid the cost of checking if m_sparse_buckets_data is empty when trying + * to find an element. + * + * TODO Remove m_sparse_buckets_data and only use a pointer instead of a + * pointer+vector to save some space in the sparse_hash object. + */ + sparse_array* m_sparse_buckets; + + size_type m_bucket_count; + size_type m_nb_elements; + size_type m_nb_deleted_buckets; + + /** + * Maximum that m_nb_elements can reach before a rehash occurs automatically + * to grow the hash table. + */ + size_type m_load_threshold_rehash; + + /** + * Maximum that m_nb_elements + m_nb_deleted_buckets can reach before cleaning + * up the buckets marked as deleted. + */ + size_type m_load_threshold_clear_deleted; + float m_max_load_factor; + }; + + } // namespace detail_sparse_hash +} // namespace tsl #endif diff --git a/Include/Pipe/Extern/sparse_map.h b/Include/Pipe/Extern/sparse_map.h index b21ba4d5..158f813c 100644 --- a/Include/Pipe/Extern/sparse_map.h +++ b/Include/Pipe/Extern/sparse_map.h @@ -24,6 +24,8 @@ #ifndef TSL_SPARSE_MAP_H #define TSL_SPARSE_MAP_H +#include "Pipe/Extern/sparse_hash.h" + #include #include #include @@ -31,770 +33,908 @@ #include #include -#include "Pipe/Extern/sparse_hash.h" - -namespace tsl { - -/** - * Implementation of a sparse hash map using open-addressing with quadratic - * probing. The goal on the hash map is to be the most memory efficient - * possible, even at low load factor, while keeping reasonable performances. - * - * `GrowthPolicy` defines how the map grows and consequently how a hash value is - * mapped to a bucket. By default the map uses - * `tsl::sh::power_of_two_growth_policy`. This policy keeps the number of - * buckets to a power of two and uses a mask to map the hash to a bucket instead - * of the slow modulo. Other growth policies are available and you may define - * your own growth policy, check `tsl::sh::power_of_two_growth_policy` for the - * interface. - * - * `ExceptionSafety` defines the exception guarantee provided by the class. By - * default only the basic exception safety is guaranteed which mean that all - * resources used by the hash map will be freed (no memory leaks) but the hash - * map may end-up in an undefined state if an exception is thrown (undefined - * here means that some elements may be missing). This can ONLY happen on rehash - * (either on insert or if `rehash` is called explicitly) and will occur if the - * Allocator can't allocate memory (`std::bad_alloc`) or if the copy constructor - * (when a nothrow move constructor is not available) throws an exception. This - * can be avoided by calling `reserve` beforehand. This basic guarantee is - * similar to the one of `google::sparse_hash_map` and `spp::sparse_hash_map`. - * It is possible to ask for the strong exception guarantee with - * `tsl::sh::exception_safety::strong`, the drawback is that the map will be - * slower on rehashes and will also need more memory on rehashes. - * - * `Sparsity` defines how much the hash set will compromise between insertion - * speed and memory usage. A high sparsity means less memory usage but longer - * insertion times, and vice-versa for low sparsity. The default - * `tsl::sh::sparsity::medium` sparsity offers a good compromise. It doesn't - * change the lookup speed. - * - * `Key` and `T` must be nothrow move constructible and/or copy constructible. - * - * If the destructor of `Key` or `T` throws an exception, the behaviour of the - * class is undefined. - * - * Iterators invalidation: - * - clear, operator=, reserve, rehash: always invalidate the iterators. - * - insert, emplace, emplace_hint, operator[]: if there is an effective - * insert, invalidate the iterators. - * - erase: always invalidate the iterators. - */ -template , - class KeyEqual = std::equal_to, - class Allocator = std::allocator>, - class GrowthPolicy = tsl::sh::power_of_two_growth_policy<2>, - tsl::sh::exception_safety ExceptionSafety = - tsl::sh::exception_safety::basic, - tsl::sh::sparsity Sparsity = tsl::sh::sparsity::medium> -class sparse_map { - private: - template - using has_is_transparent = tsl::detail_sparse_hash::has_is_transparent; - - class KeySelect { - public: - using key_type = Key; - - const key_type &operator()( - const std::pair &key_value) const noexcept { - return key_value.first; - } - - key_type &operator()(std::pair &key_value) noexcept { - return key_value.first; - } - }; - - class ValueSelect { - public: - using value_type = T; - - const value_type &operator()( - const std::pair &key_value) const noexcept { - return key_value.second; - } - - value_type &operator()(std::pair &key_value) noexcept { - return key_value.second; - } - }; - - using ht = detail_sparse_hash::sparse_hash< - std::pair, KeySelect, ValueSelect, Hash, KeyEqual, Allocator, - GrowthPolicy, ExceptionSafety, Sparsity, tsl::sh::probing::quadratic>; - - public: - using key_type = typename ht::key_type; - using mapped_type = T; - using value_type = typename ht::value_type; - using size_type = typename ht::size_type; - using difference_type = typename ht::difference_type; - using hasher = typename ht::hasher; - using key_equal = typename ht::key_equal; - using allocator_type = typename ht::allocator_type; - using reference = typename ht::reference; - using const_reference = typename ht::const_reference; - using pointer = typename ht::pointer; - using const_pointer = typename ht::const_pointer; - using iterator = typename ht::iterator; - using const_iterator = typename ht::const_iterator; - - public: - /* - * Constructors - */ - sparse_map() : sparse_map(ht::DEFAULT_INIT_BUCKET_COUNT) {} - - explicit sparse_map(size_type bucket_count, const Hash &hash = Hash(), - const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) {} - - sparse_map(size_type bucket_count, const Allocator &alloc) - : sparse_map(bucket_count, Hash(), KeyEqual(), alloc) {} - - sparse_map(size_type bucket_count, const Hash &hash, const Allocator &alloc) - : sparse_map(bucket_count, hash, KeyEqual(), alloc) {} - - explicit sparse_map(const Allocator &alloc) - : sparse_map(ht::DEFAULT_INIT_BUCKET_COUNT, alloc) {} - - template - sparse_map(InputIt first, InputIt last, - size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, - const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : sparse_map(bucket_count, hash, equal, alloc) { - insert(first, last); - } - - template - sparse_map(InputIt first, InputIt last, size_type bucket_count, - const Allocator &alloc) - : sparse_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) {} - - template - sparse_map(InputIt first, InputIt last, size_type bucket_count, - const Hash &hash, const Allocator &alloc) - : sparse_map(first, last, bucket_count, hash, KeyEqual(), alloc) {} - - sparse_map(std::initializer_list init, - size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, - const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : sparse_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) { - } - - sparse_map(std::initializer_list init, size_type bucket_count, - const Allocator &alloc) - : sparse_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), - alloc) {} - - sparse_map(std::initializer_list init, size_type bucket_count, - const Hash &hash, const Allocator &alloc) - : sparse_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), - alloc) {} - - sparse_map &operator=(std::initializer_list ilist) { - m_ht.clear(); - - m_ht.reserve(ilist.size()); - m_ht.insert(ilist.begin(), ilist.end()); - - return *this; - } - - allocator_type get_allocator() const { return m_ht.get_allocator(); } - - /* - * Iterators - */ - iterator begin() noexcept { return m_ht.begin(); } - const_iterator begin() const noexcept { return m_ht.begin(); } - const_iterator cbegin() const noexcept { return m_ht.cbegin(); } - - iterator end() noexcept { return m_ht.end(); } - const_iterator end() const noexcept { return m_ht.end(); } - const_iterator cend() const noexcept { return m_ht.cend(); } - - /* - * Capacity - */ - bool empty() const noexcept { return m_ht.empty(); } - size_type size() const noexcept { return m_ht.size(); } - size_type max_size() const noexcept { return m_ht.max_size(); } - - /* - * Modifiers - */ - void clear() noexcept { m_ht.clear(); } - - std::pair insert(const value_type &value) { - return m_ht.insert(value); - } - - template ::value>::type * = nullptr> - std::pair insert(P &&value) { - return m_ht.emplace(std::forward

(value)); - } - - std::pair insert(value_type &&value) { - return m_ht.insert(std::move(value)); - } - - iterator insert(const_iterator hint, const value_type &value) { - return m_ht.insert_hint(hint, value); - } - - template ::value>::type * = nullptr> - iterator insert(const_iterator hint, P &&value) { - return m_ht.emplace_hint(hint, std::forward

(value)); - } - - iterator insert(const_iterator hint, value_type &&value) { - return m_ht.insert_hint(hint, std::move(value)); - } - - template - void insert(InputIt first, InputIt last) { - m_ht.insert(first, last); - } - - void insert(std::initializer_list ilist) { - m_ht.insert(ilist.begin(), ilist.end()); - } - - template - std::pair insert_or_assign(const key_type &k, M &&obj) { - return m_ht.insert_or_assign(k, std::forward(obj)); - } - - template - std::pair insert_or_assign(key_type &&k, M &&obj) { - return m_ht.insert_or_assign(std::move(k), std::forward(obj)); - } - - template - iterator insert_or_assign(const_iterator hint, const key_type &k, M &&obj) { - return m_ht.insert_or_assign(hint, k, std::forward(obj)); - } - - template - iterator insert_or_assign(const_iterator hint, key_type &&k, M &&obj) { - return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); - } - - /** - * Due to the way elements are stored, emplace will need to move or copy the - * key-value once. The method is equivalent to - * `insert(value_type(std::forward(args)...));`. - * - * Mainly here for compatibility with the `std::unordered_map` interface. - */ - template - std::pair emplace(Args &&...args) { - return m_ht.emplace(std::forward(args)...); - } - - /** - * Due to the way elements are stored, emplace_hint will need to move or copy - * the key-value once. The method is equivalent to `insert(hint, - * value_type(std::forward(args)...));`. - * - * Mainly here for compatibility with the `std::unordered_map` interface. - */ - template - iterator emplace_hint(const_iterator hint, Args &&...args) { - return m_ht.emplace_hint(hint, std::forward(args)...); - } - - template - std::pair try_emplace(const key_type &k, Args &&...args) { - return m_ht.try_emplace(k, std::forward(args)...); - } - - template - std::pair try_emplace(key_type &&k, Args &&...args) { - return m_ht.try_emplace(std::move(k), std::forward(args)...); - } - - template - iterator try_emplace(const_iterator hint, const key_type &k, Args &&...args) { - return m_ht.try_emplace_hint(hint, k, std::forward(args)...); - } - - template - iterator try_emplace(const_iterator hint, key_type &&k, Args &&...args) { - return m_ht.try_emplace_hint(hint, std::move(k), - std::forward(args)...); - } - - iterator erase(iterator pos) { return m_ht.erase(pos); } - iterator erase(const_iterator pos) { return m_ht.erase(pos); } - iterator erase(const_iterator first, const_iterator last) { - return m_ht.erase(first, last); - } - size_type erase(const key_type &key) { return m_ht.erase(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - size_type erase(const key_type &key, std::size_t precalculated_hash) { - return m_ht.erase(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type erase(const K &key) { - return m_ht.erase(key); - } - - /** - * @copydoc erase(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type erase(const K &key, std::size_t precalculated_hash) { - return m_ht.erase(key, precalculated_hash); - } - - void swap(sparse_map &other) { other.m_ht.swap(m_ht); } - - /* - * Lookup - */ - T &at(const Key &key) { return m_ht.at(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - T &at(const Key &key, std::size_t precalculated_hash) { - return m_ht.at(key, precalculated_hash); - } - - const T &at(const Key &key) const { return m_ht.at(key); } - - /** - * @copydoc at(const Key& key, std::size_t precalculated_hash) - */ - const T &at(const Key &key, std::size_t precalculated_hash) const { - return m_ht.at(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - T &at(const K &key) { - return m_ht.at(key); - } - - /** - * @copydoc at(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - T &at(const K &key, std::size_t precalculated_hash) { - return m_ht.at(key, precalculated_hash); - } - - /** - * @copydoc at(const K& key) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const T &at(const K &key) const { - return m_ht.at(key); - } - - /** - * @copydoc at(const K& key, std::size_t precalculated_hash) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const T &at(const K &key, std::size_t precalculated_hash) const { - return m_ht.at(key, precalculated_hash); - } - - T &operator[](const Key &key) { return m_ht[key]; } - T &operator[](Key &&key) { return m_ht[std::move(key)]; } - - size_type count(const Key &key) const { return m_ht.count(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - size_type count(const Key &key, std::size_t precalculated_hash) const { - return m_ht.count(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type count(const K &key) const { - return m_ht.count(key); - } - - /** - * @copydoc count(const K& key) const - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type count(const K &key, std::size_t precalculated_hash) const { - return m_ht.count(key, precalculated_hash); - } - - iterator find(const Key &key) { return m_ht.find(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - iterator find(const Key &key, std::size_t precalculated_hash) { - return m_ht.find(key, precalculated_hash); - } - - const_iterator find(const Key &key) const { return m_ht.find(key); } - - /** - * @copydoc find(const Key& key, std::size_t precalculated_hash) - */ - const_iterator find(const Key &key, std::size_t precalculated_hash) const { - return m_ht.find(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - iterator find(const K &key) { - return m_ht.find(key); - } - - /** - * @copydoc find(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - iterator find(const K &key, std::size_t precalculated_hash) { - return m_ht.find(key, precalculated_hash); - } - - /** - * @copydoc find(const K& key) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const_iterator find(const K &key) const { - return m_ht.find(key); - } - - /** - * @copydoc find(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const_iterator find(const K &key, std::size_t precalculated_hash) const { - return m_ht.find(key, precalculated_hash); - } - - bool contains(const Key &key) const { return m_ht.contains(key); } - - /** - * Use the hash value 'precalculated_hash' instead of hashing the key. The - * hash value should be the same as hash_function()(key). Useful to speed-up - * the lookup if you already have the hash. - */ - bool contains(const Key &key, std::size_t precalculated_hash) const { - return m_ht.contains(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * KeyEqual::is_transparent exists. If so, K must be hashable and comparable - * to Key. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - bool contains(const K &key) const { - return m_ht.contains(key); - } - - /** - * @copydoc contains(const K& key) const - * - * Use the hash value 'precalculated_hash' instead of hashing the key. The - * hash value should be the same as hash_function()(key). Useful to speed-up - * the lookup if you already have the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - bool contains(const K &key, std::size_t precalculated_hash) const { - return m_ht.contains(key, precalculated_hash); - } - - std::pair equal_range(const Key &key) { - return m_ht.equal_range(key); - } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - std::pair equal_range(const Key &key, - std::size_t precalculated_hash) { - return m_ht.equal_range(key, precalculated_hash); - } - - std::pair equal_range(const Key &key) const { - return m_ht.equal_range(key); - } - - /** - * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) - */ - std::pair equal_range( - const Key &key, std::size_t precalculated_hash) const { - return m_ht.equal_range(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key) { - return m_ht.equal_range(key); - } - - /** - * @copydoc equal_range(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key, - std::size_t precalculated_hash) { - return m_ht.equal_range(key, precalculated_hash); - } - - /** - * @copydoc equal_range(const K& key) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key) const { - return m_ht.equal_range(key); - } - - /** - * @copydoc equal_range(const K& key, std::size_t precalculated_hash) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range( - const K &key, std::size_t precalculated_hash) const { - return m_ht.equal_range(key, precalculated_hash); - } - - /* - * Bucket interface - */ - size_type bucket_count() const { return m_ht.bucket_count(); } - size_type max_bucket_count() const { return m_ht.max_bucket_count(); } - - /* - * Hash policy - */ - float load_factor() const { return m_ht.load_factor(); } - float max_load_factor() const { return m_ht.max_load_factor(); } - void max_load_factor(float ml) { m_ht.max_load_factor(ml); } - - void rehash(size_type count) { m_ht.rehash(count); } - void reserve(size_type count) { m_ht.reserve(count); } - - /* - * Observers - */ - hasher hash_function() const { return m_ht.hash_function(); } - key_equal key_eq() const { return m_ht.key_eq(); } - - /* - * Other - */ - - /** - * Convert a `const_iterator` to an `iterator`. - */ - iterator mutable_iterator(const_iterator pos) { - return m_ht.mutable_iterator(pos); - } - - /** - * Serialize the map through the `serializer` parameter. - * - * The `serializer` parameter must be a function object that supports the - * following call: - * - `template void operator()(const U& value);` where the types - * `std::uint64_t`, `float` and `std::pair` must be supported for U. - * - * The implementation leaves binary compatibility (endianness, IEEE 754 for - * floats, ...) of the types it serializes in the hands of the `Serializer` - * function object if compatibility is required. - */ - template - void serialize(Serializer &serializer) const { - m_ht.serialize(serializer); - } - - /** - * Deserialize a previously serialized map through the `deserializer` - * parameter. - * - * The `deserializer` parameter must be a function object that supports the - * following calls: - * - `template U operator()();` where the types `std::uint64_t`, - * `float` and `std::pair` must be supported for U. - * - * If the deserialized hash map type is hash compatible with the serialized - * map, the deserialization process can be sped up by setting - * `hash_compatible` to true. To be hash compatible, the Hash, KeyEqual and - * GrowthPolicy must behave the same way than the ones used on the serialized - * map. The `std::size_t` must also be of the same size as the one on the - * platform used to serialize the map. If these criteria are not met, the - * behaviour is undefined with `hash_compatible` sets to true. - * - * The behaviour is undefined if the type `Key` and `T` of the `sparse_map` - * are not the same as the types used during serialization. - * - * The implementation leaves binary compatibility (endianness, IEEE 754 for - * floats, size of int, ...) of the types it deserializes in the hands of the - * `Deserializer` function object if compatibility is required. - */ - template - static sparse_map deserialize(Deserializer &deserializer, - bool hash_compatible = false) { - sparse_map map(0); - map.m_ht.deserialize(deserializer, hash_compatible); - - return map; - } - - friend bool operator==(const sparse_map &lhs, const sparse_map &rhs) { - if (lhs.size() != rhs.size()) { - return false; - } - - for (const auto &element_lhs : lhs) { - const auto it_element_rhs = rhs.find(element_lhs.first); - if (it_element_rhs == rhs.cend() || - element_lhs.second != it_element_rhs->second) { - return false; - } - } - - return true; - } - - friend bool operator!=(const sparse_map &lhs, const sparse_map &rhs) { - return !operator==(lhs, rhs); - } - - friend void swap(sparse_map &lhs, sparse_map &rhs) { lhs.swap(rhs); } - - private: - ht m_ht; -}; - -/** - * Same as `tsl::sparse_map`. - */ -template , - class KeyEqual = std::equal_to, - class Allocator = std::allocator>> -using sparse_pg_map = - sparse_map; -} // end namespace tsl +namespace tsl +{ + + /** + * Implementation of a sparse hash map using open-addressing with quadratic + * probing. The goal on the hash map is to be the most memory efficient + * possible, even at low load factor, while keeping reasonable performances. + * + * `GrowthPolicy` defines how the map grows and consequently how a hash value is + * mapped to a bucket. By default the map uses + * `tsl::sh::power_of_two_growth_policy`. This policy keeps the number of + * buckets to a power of two and uses a mask to map the hash to a bucket instead + * of the slow modulo. Other growth policies are available and you may define + * your own growth policy, check `tsl::sh::power_of_two_growth_policy` for the + * interface. + * + * `ExceptionSafety` defines the exception guarantee provided by the class. By + * default only the basic exception safety is guaranteed which mean that all + * resources used by the hash map will be freed (no memory leaks) but the hash + * map may end-up in an undefined state if an exception is thrown (undefined + * here means that some elements may be missing). This can ONLY happen on rehash + * (either on insert or if `rehash` is called explicitly) and will occur if the + * Allocator can't allocate memory (`std::bad_alloc`) or if the copy constructor + * (when a nothrow move constructor is not available) throws an exception. This + * can be avoided by calling `reserve` beforehand. This basic guarantee is + * similar to the one of `google::sparse_hash_map` and `spp::sparse_hash_map`. + * It is possible to ask for the strong exception guarantee with + * `tsl::sh::exception_safety::strong`, the drawback is that the map will be + * slower on rehashes and will also need more memory on rehashes. + * + * `Sparsity` defines how much the hash set will compromise between insertion + * speed and memory usage. A high sparsity means less memory usage but longer + * insertion times, and vice-versa for low sparsity. The default + * `tsl::sh::sparsity::medium` sparsity offers a good compromise. It doesn't + * change the lookup speed. + * + * `Key` and `T` must be nothrow move constructible and/or copy constructible. + * + * If the destructor of `Key` or `T` throws an exception, the behaviour of the + * class is undefined. + * + * Iterators invalidation: + * - clear, operator=, reserve, rehash: always invalidate the iterators. + * - insert, emplace, emplace_hint, operator[]: if there is an effective + * insert, invalidate the iterators. + * - erase: always invalidate the iterators. + */ + template, class KeyEqual = std::equal_to, + class Allocator = std::allocator>, + class GrowthPolicy = tsl::sh::power_of_two_growth_policy<2>, + tsl::sh::exception_safety ExceptionSafety = tsl::sh::exception_safety::basic, + tsl::sh::sparsity Sparsity = tsl::sh::sparsity::medium> + class sparse_map + { + private: + template + using has_is_transparent = tsl::detail_sparse_hash::has_is_transparent; + + class KeySelect + { + public: + using key_type = Key; + + const key_type& operator()(const std::pair& key_value) const noexcept + { + return key_value.first; + } + + key_type& operator()(std::pair& key_value) noexcept + { + return key_value.first; + } + }; + + class ValueSelect + { + public: + using value_type = T; + + const value_type& operator()(const std::pair& key_value) const noexcept + { + return key_value.second; + } + + value_type& operator()(std::pair& key_value) noexcept + { + return key_value.second; + } + }; + + using ht = detail_sparse_hash::sparse_hash, KeySelect, ValueSelect, Hash, + KeyEqual, Allocator, GrowthPolicy, ExceptionSafety, Sparsity, + tsl::sh::probing::quadratic>; + + public: + using key_type = typename ht::key_type; + using mapped_type = T; + using value_type = typename ht::value_type; + using size_type = typename ht::size_type; + using difference_type = typename ht::difference_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using allocator_type = typename ht::allocator_type; + using reference = typename ht::reference; + using const_reference = typename ht::const_reference; + using pointer = typename ht::pointer; + using const_pointer = typename ht::const_pointer; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + + public: + /* + * Constructors + */ + sparse_map() : sparse_map(ht::DEFAULT_INIT_BUCKET_COUNT) {} + + explicit sparse_map(size_type bucket_count, const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) + : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) + {} + + sparse_map(size_type bucket_count, const Allocator& alloc) + : sparse_map(bucket_count, Hash(), KeyEqual(), alloc) + {} + + sparse_map(size_type bucket_count, const Hash& hash, const Allocator& alloc) + : sparse_map(bucket_count, hash, KeyEqual(), alloc) + {} + + explicit sparse_map(const Allocator& alloc) + : sparse_map(ht::DEFAULT_INIT_BUCKET_COUNT, alloc) + {} + + template + sparse_map(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) + : sparse_map(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + + template + sparse_map(InputIt first, InputIt last, size_type bucket_count, const Allocator& alloc) + : sparse_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) + {} + + template + sparse_map(InputIt first, InputIt last, size_type bucket_count, const Hash& hash, + const Allocator& alloc) + : sparse_map(first, last, bucket_count, hash, KeyEqual(), alloc) + {} + + sparse_map(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) + : sparse_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) + {} + + sparse_map( + std::initializer_list init, size_type bucket_count, const Allocator& alloc) + : sparse_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) + {} + + sparse_map(std::initializer_list init, size_type bucket_count, const Hash& hash, + const Allocator& alloc) + : sparse_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) + {} + + sparse_map& operator=(std::initializer_list ilist) + { + m_ht.clear(); + + m_ht.reserve(ilist.size()); + m_ht.insert(ilist.begin(), ilist.end()); + + return *this; + } + + allocator_type get_allocator() const + { + return m_ht.get_allocator(); + } + + /* + * Iterators + */ + iterator begin() noexcept + { + return m_ht.begin(); + } + const_iterator begin() const noexcept + { + return m_ht.begin(); + } + const_iterator cbegin() const noexcept + { + return m_ht.cbegin(); + } + + iterator end() noexcept + { + return m_ht.end(); + } + const_iterator end() const noexcept + { + return m_ht.end(); + } + const_iterator cend() const noexcept + { + return m_ht.cend(); + } + + /* + * Capacity + */ + bool empty() const noexcept + { + return m_ht.empty(); + } + size_type size() const noexcept + { + return m_ht.size(); + } + size_type max_size() const noexcept + { + return m_ht.max_size(); + } + + /* + * Modifiers + */ + void clear() noexcept + { + m_ht.clear(); + } + + std::pair insert(const value_type& value) + { + return m_ht.insert(value); + } + + template::value>::type* = nullptr> + std::pair insert(P&& value) + { + return m_ht.emplace(std::forward

(value)); + } + + std::pair insert(value_type&& value) + { + return m_ht.insert(std::move(value)); + } + + iterator insert(const_iterator hint, const value_type& value) + { + return m_ht.insert_hint(hint, value); + } + + template::value>::type* = nullptr> + iterator insert(const_iterator hint, P&& value) + { + return m_ht.emplace_hint(hint, std::forward

(value)); + } + + iterator insert(const_iterator hint, value_type&& value) + { + return m_ht.insert_hint(hint, std::move(value)); + } + + template + void insert(InputIt first, InputIt last) + { + m_ht.insert(first, last); + } + + void insert(std::initializer_list ilist) + { + m_ht.insert(ilist.begin(), ilist.end()); + } + + template + std::pair insert_or_assign(const key_type& k, M&& obj) + { + return m_ht.insert_or_assign(k, std::forward(obj)); + } + + template + std::pair insert_or_assign(key_type&& k, M&& obj) + { + return m_ht.insert_or_assign(std::move(k), std::forward(obj)); + } + + template + iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) + { + return m_ht.insert_or_assign(hint, k, std::forward(obj)); + } + + template + iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) + { + return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); + } + + /** + * Due to the way elements are stored, emplace will need to move or copy the + * key-value once. The method is equivalent to + * `insert(value_type(std::forward(args)...));`. + * + * Mainly here for compatibility with the `std::unordered_map` interface. + */ + template + std::pair emplace(Args&&... args) + { + return m_ht.emplace(std::forward(args)...); + } + + /** + * Due to the way elements are stored, emplace_hint will need to move or copy + * the key-value once. The method is equivalent to `insert(hint, + * value_type(std::forward(args)...));`. + * + * Mainly here for compatibility with the `std::unordered_map` interface. + */ + template + iterator emplace_hint(const_iterator hint, Args&&... args) + { + return m_ht.emplace_hint(hint, std::forward(args)...); + } + + template + std::pair try_emplace(const key_type& k, Args&&... args) + { + return m_ht.try_emplace(k, std::forward(args)...); + } + + template + std::pair try_emplace(key_type&& k, Args&&... args) + { + return m_ht.try_emplace(std::move(k), std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) + { + return m_ht.try_emplace_hint(hint, k, std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) + { + return m_ht.try_emplace_hint(hint, std::move(k), std::forward(args)...); + } + + iterator erase(iterator pos) + { + return m_ht.erase(pos); + } + iterator erase(const_iterator pos) + { + return m_ht.erase(pos); + } + iterator erase(const_iterator first, const_iterator last) + { + return m_ht.erase(first, last); + } + size_type erase(const key_type& key) + { + return m_ht.erase(key); + } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + size_type erase(const key_type& key, std::size_t precalculated_hash) + { + return m_ht.erase(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template::value>::type* = nullptr> + size_type erase(const K& key) + { + return m_ht.erase(key); + } + + /** + * @copydoc erase(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template::value>::type* = nullptr> + size_type erase(const K& key, std::size_t precalculated_hash) + { + return m_ht.erase(key, precalculated_hash); + } + + void swap(sparse_map& other) + { + other.m_ht.swap(m_ht); + } + + /* + * Lookup + */ + T& at(const Key& key) + { + return m_ht.at(key); + } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + T& at(const Key& key, std::size_t precalculated_hash) + { + return m_ht.at(key, precalculated_hash); + } + + const T& at(const Key& key) const + { + return m_ht.at(key); + } + + /** + * @copydoc at(const Key& key, std::size_t precalculated_hash) + */ + const T& at(const Key& key, std::size_t precalculated_hash) const + { + return m_ht.at(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template::value>::type* = nullptr> + T& at(const K& key) + { + return m_ht.at(key); + } + + /** + * @copydoc at(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template::value>::type* = nullptr> + T& at(const K& key, std::size_t precalculated_hash) + { + return m_ht.at(key, precalculated_hash); + } + + /** + * @copydoc at(const K& key) + */ + template::value>::type* = nullptr> + const T& at(const K& key) const + { + return m_ht.at(key); + } + + /** + * @copydoc at(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + const T& at(const K& key, std::size_t precalculated_hash) const + { + return m_ht.at(key, precalculated_hash); + } + + T& operator[](const Key& key) + { + return m_ht[key]; + } + T& operator[](Key&& key) + { + return m_ht[std::move(key)]; + } + + size_type count(const Key& key) const + { + return m_ht.count(key); + } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + size_type count(const Key& key, std::size_t precalculated_hash) const + { + return m_ht.count(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template::value>::type* = nullptr> + size_type count(const K& key) const + { + return m_ht.count(key); + } + + /** + * @copydoc count(const K& key) const + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template::value>::type* = nullptr> + size_type count(const K& key, std::size_t precalculated_hash) const + { + return m_ht.count(key, precalculated_hash); + } + + iterator find(const Key& key) + { + return m_ht.find(key); + } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + iterator find(const Key& key, std::size_t precalculated_hash) + { + return m_ht.find(key, precalculated_hash); + } + + const_iterator find(const Key& key) const + { + return m_ht.find(key); + } + + /** + * @copydoc find(const Key& key, std::size_t precalculated_hash) + */ + const_iterator find(const Key& key, std::size_t precalculated_hash) const + { + return m_ht.find(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template::value>::type* = nullptr> + iterator find(const K& key) + { + return m_ht.find(key); + } + + /** + * @copydoc find(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template::value>::type* = nullptr> + iterator find(const K& key, std::size_t precalculated_hash) + { + return m_ht.find(key, precalculated_hash); + } + + /** + * @copydoc find(const K& key) + */ + template::value>::type* = nullptr> + const_iterator find(const K& key) const + { + return m_ht.find(key); + } + + /** + * @copydoc find(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template::value>::type* = nullptr> + const_iterator find(const K& key, std::size_t precalculated_hash) const + { + return m_ht.find(key, precalculated_hash); + } + + bool contains(const Key& key) const + { + return m_ht.contains(key); + } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(key). Useful to speed-up + * the lookup if you already have the hash. + */ + bool contains(const Key& key, std::size_t precalculated_hash) const + { + return m_ht.contains(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * KeyEqual::is_transparent exists. If so, K must be hashable and comparable + * to Key. + */ + template::value>::type* = nullptr> + bool contains(const K& key) const + { + return m_ht.contains(key); + } + + /** + * @copydoc contains(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(key). Useful to speed-up + * the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + bool contains(const K& key, std::size_t precalculated_hash) const + { + return m_ht.contains(key, precalculated_hash); + } + + std::pair equal_range(const Key& key) + { + return m_ht.equal_range(key); + } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) + { + return m_ht.equal_range(key, precalculated_hash); + } + + std::pair equal_range(const Key& key) const + { + return m_ht.equal_range(key); + } + + /** + * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) + */ + std::pair equal_range( + const Key& key, std::size_t precalculated_hash) const + { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) + { + return m_ht.equal_range(key); + } + + /** + * @copydoc equal_range(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) + { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * @copydoc equal_range(const K& key) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) const + { + return m_ht.equal_range(key); + } + + /** + * @copydoc equal_range(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + std::pair equal_range( + const K& key, std::size_t precalculated_hash) const + { + return m_ht.equal_range(key, precalculated_hash); + } + + /* + * Bucket interface + */ + size_type bucket_count() const + { + return m_ht.bucket_count(); + } + size_type max_bucket_count() const + { + return m_ht.max_bucket_count(); + } + + /* + * Hash policy + */ + float load_factor() const + { + return m_ht.load_factor(); + } + float max_load_factor() const + { + return m_ht.max_load_factor(); + } + void max_load_factor(float ml) + { + m_ht.max_load_factor(ml); + } + + void rehash(size_type count) + { + m_ht.rehash(count); + } + void reserve(size_type count) + { + m_ht.reserve(count); + } + + /* + * Observers + */ + hasher hash_function() const + { + return m_ht.hash_function(); + } + key_equal key_eq() const + { + return m_ht.key_eq(); + } + + /* + * Other + */ + + /** + * Convert a `const_iterator` to an `iterator`. + */ + iterator mutable_iterator(const_iterator pos) + { + return m_ht.mutable_iterator(pos); + } + + /** + * Serialize the map through the `serializer` parameter. + * + * The `serializer` parameter must be a function object that supports the + * following call: + * - `template void operator()(const U& value);` where the types + * `std::uint64_t`, `float` and `std::pair` must be supported for U. + * + * The implementation leaves binary compatibility (endianness, IEEE 754 for + * floats, ...) of the types it serializes in the hands of the `Serializer` + * function object if compatibility is required. + */ + template + void serialize(Serializer& serializer) const + { + m_ht.serialize(serializer); + } + + /** + * Deserialize a previously serialized map through the `deserializer` + * parameter. + * + * The `deserializer` parameter must be a function object that supports the + * following calls: + * - `template U operator()();` where the types `std::uint64_t`, + * `float` and `std::pair` must be supported for U. + * + * If the deserialized hash map type is hash compatible with the serialized + * map, the deserialization process can be sped up by setting + * `hash_compatible` to true. To be hash compatible, the Hash, KeyEqual and + * GrowthPolicy must behave the same way than the ones used on the serialized + * map. The `std::size_t` must also be of the same size as the one on the + * platform used to serialize the map. If these criteria are not met, the + * behaviour is undefined with `hash_compatible` sets to true. + * + * The behaviour is undefined if the type `Key` and `T` of the `sparse_map` + * are not the same as the types used during serialization. + * + * The implementation leaves binary compatibility (endianness, IEEE 754 for + * floats, size of int, ...) of the types it deserializes in the hands of the + * `Deserializer` function object if compatibility is required. + */ + template + static sparse_map deserialize(Deserializer& deserializer, bool hash_compatible = false) + { + sparse_map map(0); + map.m_ht.deserialize(deserializer, hash_compatible); + + return map; + } + + friend bool operator==(const sparse_map& lhs, const sparse_map& rhs) + { + if (lhs.size() != rhs.size()) + { + return false; + } + + for (const auto& element_lhs : lhs) + { + const auto it_element_rhs = rhs.find(element_lhs.first); + if (it_element_rhs == rhs.cend() || element_lhs.second != it_element_rhs->second) + { + return false; + } + } + + return true; + } + + friend bool operator!=(const sparse_map& lhs, const sparse_map& rhs) + { + return !operator==(lhs, rhs); + } + + friend void swap(sparse_map& lhs, sparse_map& rhs) + { + lhs.swap(rhs); + } + + private: + ht m_ht; + }; + + /** + * Same as `tsl::sparse_map`. + */ + template, class KeyEqual = std::equal_to, + class Allocator = std::allocator>> + using sparse_pg_map = + sparse_map; + +} // end namespace tsl #endif diff --git a/Include/Pipe/Extern/sparse_set.h b/Include/Pipe/Extern/sparse_set.h index d1d30743..844a6c27 100644 --- a/Include/Pipe/Extern/sparse_set.h +++ b/Include/Pipe/Extern/sparse_set.h @@ -24,6 +24,8 @@ #ifndef TSL_SPARSE_SET_H #define TSL_SPARSE_SET_H +#include "Pipe/Extern/sparse_hash.h" + #include #include #include @@ -31,625 +33,741 @@ #include #include -#include "Pipe/Extern/sparse_hash.h" - -namespace tsl { - -/** - * Implementation of a sparse hash set using open-addressing with quadratic - * probing. The goal on the hash set is to be the most memory efficient - * possible, even at low load factor, while keeping reasonable performances. - * - * `GrowthPolicy` defines how the set grows and consequently how a hash value is - * mapped to a bucket. By default the set uses - * `tsl::sh::power_of_two_growth_policy`. This policy keeps the number of - * buckets to a power of two and uses a mask to map the hash to a bucket instead - * of the slow modulo. Other growth policies are available and you may define - * your own growth policy, check `tsl::sh::power_of_two_growth_policy` for the - * interface. - * - * `ExceptionSafety` defines the exception guarantee provided by the class. By - * default only the basic exception safety is guaranteed which mean that all - * resources used by the hash set will be freed (no memory leaks) but the hash - * set may end-up in an undefined state if an exception is thrown (undefined - * here means that some elements may be missing). This can ONLY happen on rehash - * (either on insert or if `rehash` is called explicitly) and will occur if the - * Allocator can't allocate memory (`std::bad_alloc`) or if the copy constructor - * (when a nothrow move constructor is not available) throws an exception. This - * can be avoided by calling `reserve` beforehand. This basic guarantee is - * similar to the one of `google::sparse_hash_map` and `spp::sparse_hash_map`. - * It is possible to ask for the strong exception guarantee with - * `tsl::sh::exception_safety::strong`, the drawback is that the set will be - * slower on rehashes and will also need more memory on rehashes. - * - * `Sparsity` defines how much the hash set will compromise between insertion - * speed and memory usage. A high sparsity means less memory usage but longer - * insertion times, and vice-versa for low sparsity. The default - * `tsl::sh::sparsity::medium` sparsity offers a good compromise. It doesn't - * change the lookup speed. - * - * `Key` must be nothrow move constructible and/or copy constructible. - * - * If the destructor of `Key` throws an exception, the behaviour of the class is - * undefined. - * - * Iterators invalidation: - * - clear, operator=, reserve, rehash: always invalidate the iterators. - * - insert, emplace, emplace_hint: if there is an effective insert, invalidate - * the iterators. - * - erase: always invalidate the iterators. - */ -template , - class KeyEqual = std::equal_to, - class Allocator = std::allocator, - class GrowthPolicy = tsl::sh::power_of_two_growth_policy<2>, - tsl::sh::exception_safety ExceptionSafety = - tsl::sh::exception_safety::basic, - tsl::sh::sparsity Sparsity = tsl::sh::sparsity::medium> -class sparse_set { - private: - template - using has_is_transparent = tsl::detail_sparse_hash::has_is_transparent; - - class KeySelect { - public: - using key_type = Key; - - const key_type &operator()(const Key &key) const noexcept { return key; } - - key_type &operator()(Key &key) noexcept { return key; } - }; - - using ht = - detail_sparse_hash::sparse_hash; - - public: - using key_type = typename ht::key_type; - using value_type = typename ht::value_type; - using size_type = typename ht::size_type; - using difference_type = typename ht::difference_type; - using hasher = typename ht::hasher; - using key_equal = typename ht::key_equal; - using allocator_type = typename ht::allocator_type; - using reference = typename ht::reference; - using const_reference = typename ht::const_reference; - using pointer = typename ht::pointer; - using const_pointer = typename ht::const_pointer; - using iterator = typename ht::iterator; - using const_iterator = typename ht::const_iterator; - - /* - * Constructors - */ - sparse_set() : sparse_set(ht::DEFAULT_INIT_BUCKET_COUNT) {} - - explicit sparse_set(size_type bucket_count, const Hash &hash = Hash(), - const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) {} - - sparse_set(size_type bucket_count, const Allocator &alloc) - : sparse_set(bucket_count, Hash(), KeyEqual(), alloc) {} - - sparse_set(size_type bucket_count, const Hash &hash, const Allocator &alloc) - : sparse_set(bucket_count, hash, KeyEqual(), alloc) {} - - explicit sparse_set(const Allocator &alloc) - : sparse_set(ht::DEFAULT_INIT_BUCKET_COUNT, alloc) {} - - template - sparse_set(InputIt first, InputIt last, - size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, - const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : sparse_set(bucket_count, hash, equal, alloc) { - insert(first, last); - } - - template - sparse_set(InputIt first, InputIt last, size_type bucket_count, - const Allocator &alloc) - : sparse_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) {} - - template - sparse_set(InputIt first, InputIt last, size_type bucket_count, - const Hash &hash, const Allocator &alloc) - : sparse_set(first, last, bucket_count, hash, KeyEqual(), alloc) {} - - sparse_set(std::initializer_list init, - size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, - const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : sparse_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) { - } - - sparse_set(std::initializer_list init, size_type bucket_count, - const Allocator &alloc) - : sparse_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), - alloc) {} - - sparse_set(std::initializer_list init, size_type bucket_count, - const Hash &hash, const Allocator &alloc) - : sparse_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), - alloc) {} - - sparse_set &operator=(std::initializer_list ilist) { - m_ht.clear(); - - m_ht.reserve(ilist.size()); - m_ht.insert(ilist.begin(), ilist.end()); - - return *this; - } - - allocator_type get_allocator() const { return m_ht.get_allocator(); } - - /* - * Iterators - */ - iterator begin() noexcept { return m_ht.begin(); } - const_iterator begin() const noexcept { return m_ht.begin(); } - const_iterator cbegin() const noexcept { return m_ht.cbegin(); } - - iterator end() noexcept { return m_ht.end(); } - const_iterator end() const noexcept { return m_ht.end(); } - const_iterator cend() const noexcept { return m_ht.cend(); } - - /* - * Capacity - */ - bool empty() const noexcept { return m_ht.empty(); } - size_type size() const noexcept { return m_ht.size(); } - size_type max_size() const noexcept { return m_ht.max_size(); } - - /* - * Modifiers - */ - void clear() noexcept { m_ht.clear(); } - - std::pair insert(const value_type &value) { - return m_ht.insert(value); - } - - std::pair insert(value_type &&value) { - return m_ht.insert(std::move(value)); - } - - iterator insert(const_iterator hint, const value_type &value) { - return m_ht.insert_hint(hint, value); - } - - iterator insert(const_iterator hint, value_type &&value) { - return m_ht.insert_hint(hint, std::move(value)); - } - - template - void insert(InputIt first, InputIt last) { - m_ht.insert(first, last); - } - - void insert(std::initializer_list ilist) { - m_ht.insert(ilist.begin(), ilist.end()); - } - - /** - * Due to the way elements are stored, emplace will need to move or copy the - * key-value once. The method is equivalent to - * `insert(value_type(std::forward(args)...));`. - * - * Mainly here for compatibility with the `std::unordered_map` interface. - */ - template - std::pair emplace(Args &&...args) { - return m_ht.emplace(std::forward(args)...); - } - - /** - * Due to the way elements are stored, emplace_hint will need to move or copy - * the key-value once. The method is equivalent to `insert(hint, - * value_type(std::forward(args)...));`. - * - * Mainly here for compatibility with the `std::unordered_map` interface. - */ - template - iterator emplace_hint(const_iterator hint, Args &&...args) { - return m_ht.emplace_hint(hint, std::forward(args)...); - } - - iterator erase(iterator pos) { return m_ht.erase(pos); } - iterator erase(const_iterator pos) { return m_ht.erase(pos); } - iterator erase(const_iterator first, const_iterator last) { - return m_ht.erase(first, last); - } - size_type erase(const key_type &key) { return m_ht.erase(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - size_type erase(const key_type &key, std::size_t precalculated_hash) { - return m_ht.erase(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type erase(const K &key) { - return m_ht.erase(key); - } - - /** - * @copydoc erase(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type erase(const K &key, std::size_t precalculated_hash) { - return m_ht.erase(key, precalculated_hash); - } - - void swap(sparse_set &other) { other.m_ht.swap(m_ht); } - - /* - * Lookup - */ - size_type count(const Key &key) const { return m_ht.count(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - size_type count(const Key &key, std::size_t precalculated_hash) const { - return m_ht.count(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type count(const K &key) const { - return m_ht.count(key); - } - - /** - * @copydoc count(const K& key) const - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type count(const K &key, std::size_t precalculated_hash) const { - return m_ht.count(key, precalculated_hash); - } - - iterator find(const Key &key) { return m_ht.find(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - iterator find(const Key &key, std::size_t precalculated_hash) { - return m_ht.find(key, precalculated_hash); - } - - const_iterator find(const Key &key) const { return m_ht.find(key); } - - /** - * @copydoc find(const Key& key, std::size_t precalculated_hash) - */ - const_iterator find(const Key &key, std::size_t precalculated_hash) const { - return m_ht.find(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - iterator find(const K &key) { - return m_ht.find(key); - } - - /** - * @copydoc find(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - iterator find(const K &key, std::size_t precalculated_hash) { - return m_ht.find(key, precalculated_hash); - } - - /** - * @copydoc find(const K& key) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const_iterator find(const K &key) const { - return m_ht.find(key); - } - - /** - * @copydoc find(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const_iterator find(const K &key, std::size_t precalculated_hash) const { - return m_ht.find(key, precalculated_hash); - } - - bool contains(const Key &key) const { return m_ht.contains(key); } - - /** - * Use the hash value 'precalculated_hash' instead of hashing the key. The - * hash value should be the same as hash_function()(key). Useful to speed-up - * the lookup if you already have the hash. - */ - bool contains(const Key &key, std::size_t precalculated_hash) const { - return m_ht.contains(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * KeyEqual::is_transparent exists. If so, K must be hashable and comparable - * to Key. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - bool contains(const K &key) const { - return m_ht.contains(key); - } - - /** - * @copydoc contains(const K& key) const - * - * Use the hash value 'precalculated_hash' instead of hashing the key. The - * hash value should be the same as hash_function()(key). Useful to speed-up - * the lookup if you already have the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - bool contains(const K &key, std::size_t precalculated_hash) const { - return m_ht.contains(key, precalculated_hash); - } - - std::pair equal_range(const Key &key) { - return m_ht.equal_range(key); - } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - std::pair equal_range(const Key &key, - std::size_t precalculated_hash) { - return m_ht.equal_range(key, precalculated_hash); - } - - std::pair equal_range(const Key &key) const { - return m_ht.equal_range(key); - } - - /** - * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) - */ - std::pair equal_range( - const Key &key, std::size_t precalculated_hash) const { - return m_ht.equal_range(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key) { - return m_ht.equal_range(key); - } - - /** - * @copydoc equal_range(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key, - std::size_t precalculated_hash) { - return m_ht.equal_range(key, precalculated_hash); - } - - /** - * @copydoc equal_range(const K& key) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key) const { - return m_ht.equal_range(key); - } - - /** - * @copydoc equal_range(const K& key, std::size_t precalculated_hash) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range( - const K &key, std::size_t precalculated_hash) const { - return m_ht.equal_range(key, precalculated_hash); - } - - /* - * Bucket interface - */ - size_type bucket_count() const { return m_ht.bucket_count(); } - size_type max_bucket_count() const { return m_ht.max_bucket_count(); } - - /* - * Hash policy - */ - float load_factor() const { return m_ht.load_factor(); } - float max_load_factor() const { return m_ht.max_load_factor(); } - void max_load_factor(float ml) { m_ht.max_load_factor(ml); } - - void rehash(size_type count) { m_ht.rehash(count); } - void reserve(size_type count) { m_ht.reserve(count); } - - /* - * Observers - */ - hasher hash_function() const { return m_ht.hash_function(); } - key_equal key_eq() const { return m_ht.key_eq(); } - - /* - * Other - */ - - /** - * Convert a `const_iterator` to an `iterator`. - */ - iterator mutable_iterator(const_iterator pos) { - return m_ht.mutable_iterator(pos); - } - - /** - * Serialize the set through the `serializer` parameter. - * - * The `serializer` parameter must be a function object that supports the - * following call: - * - `void operator()(const U& value);` where the types `std::uint64_t`, - * `float` and `Key` must be supported for U. - * - * The implementation leaves binary compatibility (endianness, IEEE 754 for - * floats, ...) of the types it serializes in the hands of the `Serializer` - * function object if compatibility is required. - */ - template - void serialize(Serializer &serializer) const { - m_ht.serialize(serializer); - } - - /** - * Deserialize a previously serialized set through the `deserializer` - * parameter. - * - * The `deserializer` parameter must be a function object that supports the - * following calls: - * - `template U operator()();` where the types `std::uint64_t`, - * `float` and `Key` must be supported for U. - * - * If the deserialized hash set type is hash compatible with the serialized - * set, the deserialization process can be sped up by setting - * `hash_compatible` to true. To be hash compatible, the Hash, KeyEqual and - * GrowthPolicy must behave the same way than the ones used on the serialized - * set. The `std::size_t` must also be of the same size as the one on the - * platform used to serialize the set. If these criteria are not met, the - * behaviour is undefined with `hash_compatible` sets to true. - * - * The behaviour is undefined if the type `Key` of the `sparse_set` is not the - * same as the type used during serialization. - * - * The implementation leaves binary compatibility (endianness, IEEE 754 for - * floats, size of int, ...) of the types it deserializes in the hands of the - * `Deserializer` function object if compatibility is required. - */ - template - static sparse_set deserialize(Deserializer &deserializer, - bool hash_compatible = false) { - sparse_set set(0); - set.m_ht.deserialize(deserializer, hash_compatible); - - return set; - } - - friend bool operator==(const sparse_set &lhs, const sparse_set &rhs) { - if (lhs.size() != rhs.size()) { - return false; - } - - for (const auto &element_lhs : lhs) { - const auto it_element_rhs = rhs.find(element_lhs); - if (it_element_rhs == rhs.cend()) { - return false; - } - } - - return true; - } - - friend bool operator!=(const sparse_set &lhs, const sparse_set &rhs) { - return !operator==(lhs, rhs); - } - - friend void swap(sparse_set &lhs, sparse_set &rhs) { lhs.swap(rhs); } - - private: - ht m_ht; -}; - -/** - * Same as `tsl::sparse_set`. - */ -template , - class KeyEqual = std::equal_to, - class Allocator = std::allocator> -using sparse_pg_set = - sparse_set; -} // end namespace tsl +namespace tsl +{ + + /** + * Implementation of a sparse hash set using open-addressing with quadratic + * probing. The goal on the hash set is to be the most memory efficient + * possible, even at low load factor, while keeping reasonable performances. + * + * `GrowthPolicy` defines how the set grows and consequently how a hash value is + * mapped to a bucket. By default the set uses + * `tsl::sh::power_of_two_growth_policy`. This policy keeps the number of + * buckets to a power of two and uses a mask to map the hash to a bucket instead + * of the slow modulo. Other growth policies are available and you may define + * your own growth policy, check `tsl::sh::power_of_two_growth_policy` for the + * interface. + * + * `ExceptionSafety` defines the exception guarantee provided by the class. By + * default only the basic exception safety is guaranteed which mean that all + * resources used by the hash set will be freed (no memory leaks) but the hash + * set may end-up in an undefined state if an exception is thrown (undefined + * here means that some elements may be missing). This can ONLY happen on rehash + * (either on insert or if `rehash` is called explicitly) and will occur if the + * Allocator can't allocate memory (`std::bad_alloc`) or if the copy constructor + * (when a nothrow move constructor is not available) throws an exception. This + * can be avoided by calling `reserve` beforehand. This basic guarantee is + * similar to the one of `google::sparse_hash_map` and `spp::sparse_hash_map`. + * It is possible to ask for the strong exception guarantee with + * `tsl::sh::exception_safety::strong`, the drawback is that the set will be + * slower on rehashes and will also need more memory on rehashes. + * + * `Sparsity` defines how much the hash set will compromise between insertion + * speed and memory usage. A high sparsity means less memory usage but longer + * insertion times, and vice-versa for low sparsity. The default + * `tsl::sh::sparsity::medium` sparsity offers a good compromise. It doesn't + * change the lookup speed. + * + * `Key` must be nothrow move constructible and/or copy constructible. + * + * If the destructor of `Key` throws an exception, the behaviour of the class is + * undefined. + * + * Iterators invalidation: + * - clear, operator=, reserve, rehash: always invalidate the iterators. + * - insert, emplace, emplace_hint: if there is an effective insert, invalidate + * the iterators. + * - erase: always invalidate the iterators. + */ + template, class KeyEqual = std::equal_to, + class Allocator = std::allocator, + class GrowthPolicy = tsl::sh::power_of_two_growth_policy<2>, + tsl::sh::exception_safety ExceptionSafety = tsl::sh::exception_safety::basic, + tsl::sh::sparsity Sparsity = tsl::sh::sparsity::medium> + class sparse_set + { + private: + template + using has_is_transparent = tsl::detail_sparse_hash::has_is_transparent; + + class KeySelect + { + public: + using key_type = Key; + + const key_type& operator()(const Key& key) const noexcept + { + return key; + } + + key_type& operator()(Key& key) noexcept + { + return key; + } + }; + + using ht = detail_sparse_hash::sparse_hash; + + public: + using key_type = typename ht::key_type; + using value_type = typename ht::value_type; + using size_type = typename ht::size_type; + using difference_type = typename ht::difference_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using allocator_type = typename ht::allocator_type; + using reference = typename ht::reference; + using const_reference = typename ht::const_reference; + using pointer = typename ht::pointer; + using const_pointer = typename ht::const_pointer; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + + /* + * Constructors + */ + sparse_set() : sparse_set(ht::DEFAULT_INIT_BUCKET_COUNT) {} + + explicit sparse_set(size_type bucket_count, const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) + : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) + {} + + sparse_set(size_type bucket_count, const Allocator& alloc) + : sparse_set(bucket_count, Hash(), KeyEqual(), alloc) + {} + + sparse_set(size_type bucket_count, const Hash& hash, const Allocator& alloc) + : sparse_set(bucket_count, hash, KeyEqual(), alloc) + {} + + explicit sparse_set(const Allocator& alloc) + : sparse_set(ht::DEFAULT_INIT_BUCKET_COUNT, alloc) + {} + + template + sparse_set(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) + : sparse_set(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + + template + sparse_set(InputIt first, InputIt last, size_type bucket_count, const Allocator& alloc) + : sparse_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) + {} + + template + sparse_set(InputIt first, InputIt last, size_type bucket_count, const Hash& hash, + const Allocator& alloc) + : sparse_set(first, last, bucket_count, hash, KeyEqual(), alloc) + {} + + sparse_set(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) + : sparse_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) + {} + + sparse_set( + std::initializer_list init, size_type bucket_count, const Allocator& alloc) + : sparse_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) + {} + + sparse_set(std::initializer_list init, size_type bucket_count, const Hash& hash, + const Allocator& alloc) + : sparse_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) + {} + + sparse_set& operator=(std::initializer_list ilist) + { + m_ht.clear(); + + m_ht.reserve(ilist.size()); + m_ht.insert(ilist.begin(), ilist.end()); + + return *this; + } + + allocator_type get_allocator() const + { + return m_ht.get_allocator(); + } + + /* + * Iterators + */ + iterator begin() noexcept + { + return m_ht.begin(); + } + const_iterator begin() const noexcept + { + return m_ht.begin(); + } + const_iterator cbegin() const noexcept + { + return m_ht.cbegin(); + } + + iterator end() noexcept + { + return m_ht.end(); + } + const_iterator end() const noexcept + { + return m_ht.end(); + } + const_iterator cend() const noexcept + { + return m_ht.cend(); + } + + /* + * Capacity + */ + bool empty() const noexcept + { + return m_ht.empty(); + } + size_type size() const noexcept + { + return m_ht.size(); + } + size_type max_size() const noexcept + { + return m_ht.max_size(); + } + + /* + * Modifiers + */ + void clear() noexcept + { + m_ht.clear(); + } + + std::pair insert(const value_type& value) + { + return m_ht.insert(value); + } + + std::pair insert(value_type&& value) + { + return m_ht.insert(std::move(value)); + } + + iterator insert(const_iterator hint, const value_type& value) + { + return m_ht.insert_hint(hint, value); + } + + iterator insert(const_iterator hint, value_type&& value) + { + return m_ht.insert_hint(hint, std::move(value)); + } + + template + void insert(InputIt first, InputIt last) + { + m_ht.insert(first, last); + } + + void insert(std::initializer_list ilist) + { + m_ht.insert(ilist.begin(), ilist.end()); + } + + /** + * Due to the way elements are stored, emplace will need to move or copy the + * key-value once. The method is equivalent to + * `insert(value_type(std::forward(args)...));`. + * + * Mainly here for compatibility with the `std::unordered_map` interface. + */ + template + std::pair emplace(Args&&... args) + { + return m_ht.emplace(std::forward(args)...); + } + + /** + * Due to the way elements are stored, emplace_hint will need to move or copy + * the key-value once. The method is equivalent to `insert(hint, + * value_type(std::forward(args)...));`. + * + * Mainly here for compatibility with the `std::unordered_map` interface. + */ + template + iterator emplace_hint(const_iterator hint, Args&&... args) + { + return m_ht.emplace_hint(hint, std::forward(args)...); + } + + iterator erase(iterator pos) + { + return m_ht.erase(pos); + } + iterator erase(const_iterator pos) + { + return m_ht.erase(pos); + } + iterator erase(const_iterator first, const_iterator last) + { + return m_ht.erase(first, last); + } + size_type erase(const key_type& key) + { + return m_ht.erase(key); + } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + size_type erase(const key_type& key, std::size_t precalculated_hash) + { + return m_ht.erase(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template::value>::type* = nullptr> + size_type erase(const K& key) + { + return m_ht.erase(key); + } + + /** + * @copydoc erase(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template::value>::type* = nullptr> + size_type erase(const K& key, std::size_t precalculated_hash) + { + return m_ht.erase(key, precalculated_hash); + } + + void swap(sparse_set& other) + { + other.m_ht.swap(m_ht); + } + + /* + * Lookup + */ + size_type count(const Key& key) const + { + return m_ht.count(key); + } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + size_type count(const Key& key, std::size_t precalculated_hash) const + { + return m_ht.count(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template::value>::type* = nullptr> + size_type count(const K& key) const + { + return m_ht.count(key); + } + + /** + * @copydoc count(const K& key) const + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template::value>::type* = nullptr> + size_type count(const K& key, std::size_t precalculated_hash) const + { + return m_ht.count(key, precalculated_hash); + } + + iterator find(const Key& key) + { + return m_ht.find(key); + } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + iterator find(const Key& key, std::size_t precalculated_hash) + { + return m_ht.find(key, precalculated_hash); + } + + const_iterator find(const Key& key) const + { + return m_ht.find(key); + } + + /** + * @copydoc find(const Key& key, std::size_t precalculated_hash) + */ + const_iterator find(const Key& key, std::size_t precalculated_hash) const + { + return m_ht.find(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template::value>::type* = nullptr> + iterator find(const K& key) + { + return m_ht.find(key); + } + + /** + * @copydoc find(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template::value>::type* = nullptr> + iterator find(const K& key, std::size_t precalculated_hash) + { + return m_ht.find(key, precalculated_hash); + } + + /** + * @copydoc find(const K& key) + */ + template::value>::type* = nullptr> + const_iterator find(const K& key) const + { + return m_ht.find(key); + } + + /** + * @copydoc find(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template::value>::type* = nullptr> + const_iterator find(const K& key, std::size_t precalculated_hash) const + { + return m_ht.find(key, precalculated_hash); + } + + bool contains(const Key& key) const + { + return m_ht.contains(key); + } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(key). Useful to speed-up + * the lookup if you already have the hash. + */ + bool contains(const Key& key, std::size_t precalculated_hash) const + { + return m_ht.contains(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * KeyEqual::is_transparent exists. If so, K must be hashable and comparable + * to Key. + */ + template::value>::type* = nullptr> + bool contains(const K& key) const + { + return m_ht.contains(key); + } + + /** + * @copydoc contains(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(key). Useful to speed-up + * the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + bool contains(const K& key, std::size_t precalculated_hash) const + { + return m_ht.contains(key, precalculated_hash); + } + + std::pair equal_range(const Key& key) + { + return m_ht.equal_range(key); + } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) + { + return m_ht.equal_range(key, precalculated_hash); + } + + std::pair equal_range(const Key& key) const + { + return m_ht.equal_range(key); + } + + /** + * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) + */ + std::pair equal_range( + const Key& key, std::size_t precalculated_hash) const + { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) + { + return m_ht.equal_range(key); + } + + /** + * @copydoc equal_range(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) + { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * @copydoc equal_range(const K& key) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) const + { + return m_ht.equal_range(key); + } + + /** + * @copydoc equal_range(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + std::pair equal_range( + const K& key, std::size_t precalculated_hash) const + { + return m_ht.equal_range(key, precalculated_hash); + } + + /* + * Bucket interface + */ + size_type bucket_count() const + { + return m_ht.bucket_count(); + } + size_type max_bucket_count() const + { + return m_ht.max_bucket_count(); + } + + /* + * Hash policy + */ + float load_factor() const + { + return m_ht.load_factor(); + } + float max_load_factor() const + { + return m_ht.max_load_factor(); + } + void max_load_factor(float ml) + { + m_ht.max_load_factor(ml); + } + + void rehash(size_type count) + { + m_ht.rehash(count); + } + void reserve(size_type count) + { + m_ht.reserve(count); + } + + /* + * Observers + */ + hasher hash_function() const + { + return m_ht.hash_function(); + } + key_equal key_eq() const + { + return m_ht.key_eq(); + } + + /* + * Other + */ + + /** + * Convert a `const_iterator` to an `iterator`. + */ + iterator mutable_iterator(const_iterator pos) + { + return m_ht.mutable_iterator(pos); + } + + /** + * Serialize the set through the `serializer` parameter. + * + * The `serializer` parameter must be a function object that supports the + * following call: + * - `void operator()(const U& value);` where the types `std::uint64_t`, + * `float` and `Key` must be supported for U. + * + * The implementation leaves binary compatibility (endianness, IEEE 754 for + * floats, ...) of the types it serializes in the hands of the `Serializer` + * function object if compatibility is required. + */ + template + void serialize(Serializer& serializer) const + { + m_ht.serialize(serializer); + } + + /** + * Deserialize a previously serialized set through the `deserializer` + * parameter. + * + * The `deserializer` parameter must be a function object that supports the + * following calls: + * - `template U operator()();` where the types `std::uint64_t`, + * `float` and `Key` must be supported for U. + * + * If the deserialized hash set type is hash compatible with the serialized + * set, the deserialization process can be sped up by setting + * `hash_compatible` to true. To be hash compatible, the Hash, KeyEqual and + * GrowthPolicy must behave the same way than the ones used on the serialized + * set. The `std::size_t` must also be of the same size as the one on the + * platform used to serialize the set. If these criteria are not met, the + * behaviour is undefined with `hash_compatible` sets to true. + * + * The behaviour is undefined if the type `Key` of the `sparse_set` is not the + * same as the type used during serialization. + * + * The implementation leaves binary compatibility (endianness, IEEE 754 for + * floats, size of int, ...) of the types it deserializes in the hands of the + * `Deserializer` function object if compatibility is required. + */ + template + static sparse_set deserialize(Deserializer& deserializer, bool hash_compatible = false) + { + sparse_set set(0); + set.m_ht.deserialize(deserializer, hash_compatible); + + return set; + } + + friend bool operator==(const sparse_set& lhs, const sparse_set& rhs) + { + if (lhs.size() != rhs.size()) + { + return false; + } + + for (const auto& element_lhs : lhs) + { + const auto it_element_rhs = rhs.find(element_lhs); + if (it_element_rhs == rhs.cend()) + { + return false; + } + } + + return true; + } + + friend bool operator!=(const sparse_set& lhs, const sparse_set& rhs) + { + return !operator==(lhs, rhs); + } + + friend void swap(sparse_set& lhs, sparse_set& rhs) + { + lhs.swap(rhs); + } + + private: + ht m_ht; + }; + + /** + * Same as `tsl::sparse_set`. + */ + template, class KeyEqual = std::equal_to, + class Allocator = std::allocator> + using sparse_pg_set = sparse_set; + +} // end namespace tsl #endif diff --git a/Include/Pipe/Extern/utf8.h b/Include/Pipe/Extern/utf8.h index 28244378..6d5c1c9d 100644 --- a/Include/Pipe/Extern/utf8.h +++ b/Include/Pipe/Extern/utf8.h @@ -34,8 +34,8 @@ and set it to one of the values used by the __cplusplus predefined macro. For instance, #define UTF_CPP_CPLUSPLUS 199711L -will cause the UTF-8 CPP library to use only types and language features available in the C++ 98 standard. -Some library features will be disabled. +will cause the UTF-8 CPP library to use only types and language features available in the C++ 98 +standard. Some library features will be disabled. If you leave UTF_CPP_CPLUSPLUS undefined, it will be internally assigned to __cplusplus. */ @@ -43,4 +43,4 @@ If you leave UTF_CPP_CPLUSPLUS undefined, it will be internally assigned to __cp #include "Pipe/Extern/utf8/checked.h" #include "Pipe/Extern/utf8/unchecked.h" -#endif // header guard +#endif // header guard diff --git a/Include/Pipe/Extern/utf8/checked.h b/Include/Pipe/Extern/utf8/checked.h index d0dc4aa9..4b159fa1 100644 --- a/Include/Pipe/Extern/utf8/checked.h +++ b/Include/Pipe/Extern/utf8/checked.h @@ -29,331 +29,372 @@ DEALINGS IN THE SOFTWARE. #define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #include "Pipe/Extern/utf8/core.h" + #include + namespace utf8 { - // Base for the exceptions that may be thrown from the library - class exception : public ::std::exception { - }; - - // Exceptions that may be thrown from the library functions. - class invalid_code_point : public exception { - utfchar32_t cp; - public: - invalid_code_point(utfchar32_t codepoint) : cp(codepoint) {} - virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE { return "Invalid code point"; } - utfchar32_t code_point() const {return cp;} - }; - - class invalid_utf8 : public exception { - utfchar8_t u8; - public: - invalid_utf8 (utfchar8_t u) : u8(u) {} - invalid_utf8 (char c) : u8(static_cast(c)) {} - virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE { return "Invalid UTF-8"; } - utfchar8_t utf8_octet() const {return u8;} - }; - - class invalid_utf16 : public exception { - utfchar16_t u16; - public: - invalid_utf16 (utfchar16_t u) : u16(u) {} - virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE { return "Invalid UTF-16"; } - utfchar16_t utf16_word() const {return u16;} - }; - - class not_enough_room : public exception { - public: - virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE { return "Not enough space"; } - }; - - /// The library API - functions intended to be called by the users - - template - octet_iterator append(utfchar32_t cp, octet_iterator result) - { - if (!utf8::internal::is_code_point_valid(cp)) - throw invalid_code_point(cp); - - return internal::append(cp, result); - } - - inline void append(utfchar32_t cp, std::string& s) - { - append(cp, std::back_inserter(s)); - } - - template - word_iterator append16(utfchar32_t cp, word_iterator result) - { - if (!utf8::internal::is_code_point_valid(cp)) - throw invalid_code_point(cp); - - return internal::append16(cp, result); - } - - template - output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, utfchar32_t replacement) - { - while (start != end) { - octet_iterator sequence_start = start; - internal::utf_error err_code = utf8::internal::validate_next(start, end); - switch (err_code) { - case internal::UTF8_OK : - for (octet_iterator it = sequence_start; it != start; ++it) - *out++ = *it; - break; - case internal::NOT_ENOUGH_ROOM: - out = utf8::append (replacement, out); - start = end; - break; - case internal::INVALID_LEAD: - out = utf8::append (replacement, out); - ++start; - break; - case internal::INCOMPLETE_SEQUENCE: - case internal::OVERLONG_SEQUENCE: - case internal::INVALID_CODE_POINT: - out = utf8::append (replacement, out); - ++start; - // just one replacement mark for the sequence - while (start != end && utf8::internal::is_trail(*start)) - ++start; - break; - } - } - return out; - } - - template - inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) - { - static const utfchar32_t replacement_marker = utf8::internal::mask16(0xfffd); - return utf8::replace_invalid(start, end, out, replacement_marker); - } - - inline std::string replace_invalid(const std::string& s, utfchar32_t replacement) - { - std::string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); - return result; - } - - inline std::string replace_invalid(const std::string& s) - { - std::string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - template - utfchar32_t next(octet_iterator& it, octet_iterator end) - { - utfchar32_t cp = 0; - internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); - switch (err_code) { - case internal::UTF8_OK : - break; - case internal::NOT_ENOUGH_ROOM : - throw not_enough_room(); - case internal::INVALID_LEAD : - case internal::INCOMPLETE_SEQUENCE : - case internal::OVERLONG_SEQUENCE : - throw invalid_utf8(static_cast(*it)); - case internal::INVALID_CODE_POINT : - throw invalid_code_point(cp); - } - return cp; - } - - template - utfchar32_t next16(word_iterator& it, word_iterator end) - { - utfchar32_t cp = 0; - internal::utf_error err_code = utf8::internal::validate_next16(it, end, cp); - if (err_code == internal::NOT_ENOUGH_ROOM) - throw not_enough_room(); - return cp; - } - - template - utfchar32_t peek_next(octet_iterator it, octet_iterator end) - { - return utf8::next(it, end); - } - - template - utfchar32_t prior(octet_iterator& it, octet_iterator start) - { - // can't do much if it == start - if (it == start) - throw not_enough_room(); - - octet_iterator end = it; - // Go back until we hit either a lead octet or start - while (utf8::internal::is_trail(*(--it))) - if (it == start) - throw invalid_utf8(*it); // error - no lead byte in the sequence - return utf8::peek_next(it, end); - } - - template - void advance (octet_iterator& it, distance_type n, octet_iterator end) - { - const distance_type zero(0); - if (n < zero) { - // backward - for (distance_type i = n; i < zero; ++i) - utf8::prior(it, end); - } else { - // forward - for (distance_type i = zero; i < n; ++i) - utf8::next(it, end); - } - } - - template - typename std::iterator_traits::difference_type - distance (octet_iterator first, octet_iterator last) - { - typename std::iterator_traits::difference_type dist; - for (dist = 0; first < last; ++dist) - utf8::next(first, last); - return dist; - } - - template - octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) - { - while (start != end) { - utfchar32_t cp = utf8::internal::mask16(*start++); - // Take care of surrogate pairs first - if (utf8::internal::is_lead_surrogate(cp)) { - if (start != end) { - const utfchar32_t trail_surrogate = utf8::internal::mask16(*start++); - if (utf8::internal::is_trail_surrogate(trail_surrogate)) - cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; - else - throw invalid_utf16(static_cast(trail_surrogate)); - } - else - throw invalid_utf16(static_cast(cp)); - - } - // Lone trail surrogate - else if (utf8::internal::is_trail_surrogate(cp)) - throw invalid_utf16(static_cast(cp)); - - result = utf8::append(cp, result); - } - return result; - } - - template - u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) - { - while (start < end) { - const utfchar32_t cp = utf8::next(start, end); - if (cp > 0xffff) { //make a surrogate pair - *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); - *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); - } - else - *result++ = static_cast(cp); - } - return result; - } - - template - octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) - { - while (start != end) - result = utf8::append(*(start++), result); - - return result; - } - - template - u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) - { - while (start < end) - (*result++) = utf8::next(start, end); - - return result; - } - - // The iterator class - template - class iterator { - octet_iterator it; - octet_iterator range_start; - octet_iterator range_end; - public: - typedef utfchar32_t value_type; - typedef utfchar32_t* pointer; - typedef utfchar32_t& reference; - typedef std::ptrdiff_t difference_type; - typedef std::bidirectional_iterator_tag iterator_category; - iterator () {} - explicit iterator (const octet_iterator& octet_it, - const octet_iterator& rangestart, - const octet_iterator& rangeend) : - it(octet_it), range_start(rangestart), range_end(rangeend) - { - if (it < range_start || it > range_end) - throw std::out_of_range("Invalid utf-8 iterator position"); - } - // the default "big three" are OK - octet_iterator base () const { return it; } - utfchar32_t operator * () const - { - octet_iterator temp = it; - return utf8::next(temp, range_end); - } - bool operator == (const iterator& rhs) const - { - if (range_start != rhs.range_start || range_end != rhs.range_end) - throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); - return (it == rhs.it); - } - bool operator != (const iterator& rhs) const - { - return !(operator == (rhs)); - } - iterator& operator ++ () - { - utf8::next(it, range_end); - return *this; - } - iterator operator ++ (int) - { - iterator temp = *this; - utf8::next(it, range_end); - return temp; - } - iterator& operator -- () - { - utf8::prior(it, range_start); - return *this; - } - iterator operator -- (int) - { - iterator temp = *this; - utf8::prior(it, range_start); - return temp; - } - }; // class iterator - -} // namespace utf8 - -#if UTF_CPP_CPLUSPLUS >= 202002L // C++ 20 or later -#include "cpp20.h" -#elif UTF_CPP_CPLUSPLUS >= 201703L // C++ 17 or later -#include "cpp17.h" -#elif UTF_CPP_CPLUSPLUS >= 201103L // C++ 11 or later -#include "cpp11.h" -#endif // C++ 11 or later - -#endif //header guard - + // Base for the exceptions that may be thrown from the library + class exception : public ::std::exception + {}; + + // Exceptions that may be thrown from the library functions. + class invalid_code_point : public exception + { + utfchar32_t cp; + + public: + invalid_code_point(utfchar32_t codepoint) : cp(codepoint) {} + virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE + { + return "Invalid code point"; + } + utfchar32_t code_point() const + { + return cp; + } + }; + + class invalid_utf8 : public exception + { + utfchar8_t u8; + + public: + invalid_utf8(utfchar8_t u) : u8(u) {} + invalid_utf8(char c) : u8(static_cast(c)) {} + virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE + { + return "Invalid UTF-8"; + } + utfchar8_t utf8_octet() const + { + return u8; + } + }; + + class invalid_utf16 : public exception + { + utfchar16_t u16; + + public: + invalid_utf16(utfchar16_t u) : u16(u) {} + virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE + { + return "Invalid UTF-16"; + } + utfchar16_t utf16_word() const + { + return u16; + } + }; + + class not_enough_room : public exception + { + public: + virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE + { + return "Not enough space"; + } + }; + + /// The library API - functions intended to be called by the users + + template + octet_iterator append(utfchar32_t cp, octet_iterator result) + { + if (!utf8::internal::is_code_point_valid(cp)) + throw invalid_code_point(cp); + + return internal::append(cp, result); + } + + inline void append(utfchar32_t cp, std::string& s) + { + append(cp, std::back_inserter(s)); + } + + template + word_iterator append16(utfchar32_t cp, word_iterator result) + { + if (!utf8::internal::is_code_point_valid(cp)) + throw invalid_code_point(cp); + + return internal::append16(cp, result); + } + + template + output_iterator replace_invalid( + octet_iterator start, octet_iterator end, output_iterator out, utfchar32_t replacement) + { + while (start != end) + { + octet_iterator sequence_start = start; + internal::utf_error err_code = utf8::internal::validate_next(start, end); + switch (err_code) + { + case internal::UTF8_OK: + for (octet_iterator it = sequence_start; it != start; ++it) + *out++ = *it; + break; + case internal::NOT_ENOUGH_ROOM: + out = utf8::append(replacement, out); + start = end; + break; + case internal::INVALID_LEAD: + out = utf8::append(replacement, out); + ++start; + break; + case internal::INCOMPLETE_SEQUENCE: + case internal::OVERLONG_SEQUENCE: + case internal::INVALID_CODE_POINT: + out = utf8::append(replacement, out); + ++start; + // just one replacement mark for the sequence + while (start != end && utf8::internal::is_trail(*start)) + ++start; + break; + } + } + return out; + } + + template + inline output_iterator replace_invalid( + octet_iterator start, octet_iterator end, output_iterator out) + { + static const utfchar32_t replacement_marker = utf8::internal::mask16(0xfffd); + return utf8::replace_invalid(start, end, out, replacement_marker); + } + + inline std::string replace_invalid(const std::string& s, utfchar32_t replacement) + { + std::string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); + return result; + } + + inline std::string replace_invalid(const std::string& s) + { + std::string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + template + utfchar32_t next(octet_iterator& it, octet_iterator end) + { + utfchar32_t cp = 0; + internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); + switch (err_code) + { + case internal::UTF8_OK: break; + case internal::NOT_ENOUGH_ROOM: throw not_enough_room(); + case internal::INVALID_LEAD: + case internal::INCOMPLETE_SEQUENCE: + case internal::OVERLONG_SEQUENCE: throw invalid_utf8(static_cast(*it)); + case internal::INVALID_CODE_POINT: throw invalid_code_point(cp); + } + return cp; + } + + template + utfchar32_t next16(word_iterator& it, word_iterator end) + { + utfchar32_t cp = 0; + internal::utf_error err_code = utf8::internal::validate_next16(it, end, cp); + if (err_code == internal::NOT_ENOUGH_ROOM) + throw not_enough_room(); + return cp; + } + + template + utfchar32_t peek_next(octet_iterator it, octet_iterator end) + { + return utf8::next(it, end); + } + + template + utfchar32_t prior(octet_iterator& it, octet_iterator start) + { + // can't do much if it == start + if (it == start) + throw not_enough_room(); + + octet_iterator end = it; + // Go back until we hit either a lead octet or start + while (utf8::internal::is_trail(*(--it))) + if (it == start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + return utf8::peek_next(it, end); + } + + template + void advance(octet_iterator& it, distance_type n, octet_iterator end) + { + const distance_type zero(0); + if (n < zero) + { + // backward + for (distance_type i = n; i < zero; ++i) + utf8::prior(it, end); + } + else + { + // forward + for (distance_type i = zero; i < n; ++i) + utf8::next(it, end); + } + } + + template + typename std::iterator_traits::difference_type distance( + octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::next(first, last); + return dist; + } + + template + octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) + { + utfchar32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) + { + if (start != end) + { + const utfchar32_t trail_surrogate = utf8::internal::mask16(*start++); + if (utf8::internal::is_trail_surrogate(trail_surrogate)) + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + else + throw invalid_utf16(static_cast(trail_surrogate)); + } + else + throw invalid_utf16(static_cast(cp)); + } + // Lone trail surrogate + else if (utf8::internal::is_trail_surrogate(cp)) + throw invalid_utf16(static_cast(cp)); + + result = utf8::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start < end) + { + const utfchar32_t cp = utf8::next(start, end); + if (cp > 0xffff) + { // make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8(u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32(octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start < end) + (*result++) = utf8::next(start, end); + + return result; + } + + // The iterator class + template + class iterator + { + octet_iterator it; + octet_iterator range_start; + octet_iterator range_end; + + public: + typedef utfchar32_t value_type; + typedef utfchar32_t* pointer; + typedef utfchar32_t& reference; + typedef std::ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + iterator() {} + explicit iterator(const octet_iterator& octet_it, const octet_iterator& rangestart, + const octet_iterator& rangeend) + : it(octet_it), range_start(rangestart), range_end(rangeend) + { + if (it < range_start || it > range_end) + throw std::out_of_range("Invalid utf-8 iterator position"); + } + // the default "big three" are OK + octet_iterator base() const + { + return it; + } + utfchar32_t operator*() const + { + octet_iterator temp = it; + return utf8::next(temp, range_end); + } + bool operator==(const iterator& rhs) const + { + if (range_start != rhs.range_start || range_end != rhs.range_end) + throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); + return (it == rhs.it); + } + bool operator!=(const iterator& rhs) const + { + return !(operator==(rhs)); + } + iterator& operator++() + { + utf8::next(it, range_end); + return *this; + } + iterator operator++(int) + { + iterator temp = *this; + utf8::next(it, range_end); + return temp; + } + iterator& operator--() + { + utf8::prior(it, range_start); + return *this; + } + iterator operator--(int) + { + iterator temp = *this; + utf8::prior(it, range_start); + return temp; + } + }; // class iterator + +} // namespace utf8 + +#if UTF_CPP_CPLUSPLUS >= 202002L // C++ 20 or later + #include "cpp20.h" +#elif UTF_CPP_CPLUSPLUS >= 201703L // C++ 17 or later + #include "cpp17.h" +#elif UTF_CPP_CPLUSPLUS >= 201103L // C++ 11 or later + #include "cpp11.h" +#endif // C++ 11 or later + +#endif // header guard diff --git a/Include/Pipe/Extern/utf8/cpp11.h b/Include/Pipe/Extern/utf8/cpp11.h index 3c17cdc3..786a9be9 100644 --- a/Include/Pipe/Extern/utf8/cpp11.h +++ b/Include/Pipe/Extern/utf8/cpp11.h @@ -32,39 +32,38 @@ DEALINGS IN THE SOFTWARE. namespace utf8 { - inline void append16(utfchar32_t cp, std::u16string& s) - { - append16(cp, std::back_inserter(s)); - } + inline void append16(utfchar32_t cp, std::u16string& s) + { + append16(cp, std::back_inserter(s)); + } - inline std::string utf16to8(const std::u16string& s) - { - std::string result; - utf16to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } + inline std::string utf16to8(const std::u16string& s) + { + std::string result; + utf16to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } - inline std::u16string utf8to16(const std::string& s) - { - std::u16string result; - utf8to16(s.begin(), s.end(), std::back_inserter(result)); - return result; - } + inline std::u16string utf8to16(const std::string& s) + { + std::u16string result; + utf8to16(s.begin(), s.end(), std::back_inserter(result)); + return result; + } - inline std::string utf32to8(const std::u32string& s) - { - std::string result; - utf32to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } + inline std::string utf32to8(const std::u32string& s) + { + std::string result; + utf32to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } - inline std::u32string utf8to32(const std::string& s) - { - std::u32string result; - utf8to32(s.begin(), s.end(), std::back_inserter(result)); - return result; - } -} // namespace utf8 - -#endif // header guard + inline std::u32string utf8to32(const std::string& s) + { + std::u32string result; + utf8to32(s.begin(), s.end(), std::back_inserter(result)); + return result; + } +} // namespace utf8 +#endif // header guard diff --git a/Include/Pipe/Extern/utf8/cpp17.h b/Include/Pipe/Extern/utf8/cpp17.h index 44e515eb..7131e232 100644 --- a/Include/Pipe/Extern/utf8/cpp17.h +++ b/Include/Pipe/Extern/utf8/cpp17.h @@ -32,65 +32,65 @@ DEALINGS IN THE SOFTWARE. namespace utf8 { - inline std::string utf16to8(std::u16string_view s) - { - std::string result; - utf16to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u16string utf8to16(std::string_view s) - { - std::u16string result; - utf8to16(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::string utf32to8(std::u32string_view s) - { - std::string result; - utf32to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u32string utf8to32(std::string_view s) - { - std::u32string result; - utf8to32(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::size_t find_invalid(std::string_view s) - { - std::string_view::const_iterator invalid = find_invalid(s.begin(), s.end()); - return (invalid == s.end()) ? std::string_view::npos : static_cast(invalid - s.begin()); - } - - inline bool is_valid(std::string_view s) - { - return is_valid(s.begin(), s.end()); - } - - inline std::string replace_invalid(std::string_view s, char32_t replacement) - { - std::string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); - return result; - } - - inline std::string replace_invalid(std::string_view s) - { - std::string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline bool starts_with_bom(std::string_view s) - { - return starts_with_bom(s.begin(), s.end()); - } - -} // namespace utf8 - -#endif // header guard - + inline std::string utf16to8(std::u16string_view s) + { + std::string result; + utf16to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u16string utf8to16(std::string_view s) + { + std::u16string result; + utf8to16(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::string utf32to8(std::u32string_view s) + { + std::string result; + utf32to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u32string utf8to32(std::string_view s) + { + std::u32string result; + utf8to32(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::size_t find_invalid(std::string_view s) + { + std::string_view::const_iterator invalid = find_invalid(s.begin(), s.end()); + return (invalid == s.end()) ? std::string_view::npos + : static_cast(invalid - s.begin()); + } + + inline bool is_valid(std::string_view s) + { + return is_valid(s.begin(), s.end()); + } + + inline std::string replace_invalid(std::string_view s, char32_t replacement) + { + std::string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); + return result; + } + + inline std::string replace_invalid(std::string_view s) + { + std::string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline bool starts_with_bom(std::string_view s) + { + return starts_with_bom(s.begin(), s.end()); + } + +} // namespace utf8 + +#endif // header guard diff --git a/Include/Pipe/Extern/utf8/cpp20.h b/Include/Pipe/Extern/utf8/cpp20.h index be6ce1b2..6e16bad5 100644 --- a/Include/Pipe/Extern/utf8/cpp20.h +++ b/Include/Pipe/Extern/utf8/cpp20.h @@ -32,93 +32,93 @@ DEALINGS IN THE SOFTWARE. namespace utf8 { - inline std::u8string utf16tou8(const std::u16string& s) - { - std::u8string result; - utf16to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u8string utf16tou8(std::u16string_view s) - { - std::u8string result; - utf16to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u16string utf8to16(const std::u8string& s) - { - std::u16string result; - utf8to16(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u16string utf8to16(const std::u8string_view& s) - { - std::u16string result; - utf8to16(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u8string utf32tou8(const std::u32string& s) - { - std::u8string result; - utf32to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u8string utf32tou8(const std::u32string_view& s) - { - std::u8string result; - utf32to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u32string utf8to32(const std::u8string& s) - { - std::u32string result; - utf8to32(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u32string utf8to32(const std::u8string_view& s) - { - std::u32string result; - utf8to32(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::size_t find_invalid(const std::u8string& s) - { - std::u8string::const_iterator invalid = find_invalid(s.begin(), s.end()); - return (invalid == s.end()) ? std::string_view::npos : static_cast(invalid - s.begin()); - } - - inline bool is_valid(const std::u8string& s) - { - return is_valid(s.begin(), s.end()); - } - - inline std::u8string replace_invalid(const std::u8string& s, char32_t replacement) - { - std::u8string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); - return result; - } - - inline std::u8string replace_invalid(const std::u8string& s) - { - std::u8string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline bool starts_with_bom(const std::u8string& s) - { - return starts_with_bom(s.begin(), s.end()); - } - -} // namespace utf8 - -#endif // header guard - + inline std::u8string utf16tou8(const std::u16string& s) + { + std::u8string result; + utf16to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u8string utf16tou8(std::u16string_view s) + { + std::u8string result; + utf16to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u16string utf8to16(const std::u8string& s) + { + std::u16string result; + utf8to16(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u16string utf8to16(const std::u8string_view& s) + { + std::u16string result; + utf8to16(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u8string utf32tou8(const std::u32string& s) + { + std::u8string result; + utf32to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u8string utf32tou8(const std::u32string_view& s) + { + std::u8string result; + utf32to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u32string utf8to32(const std::u8string& s) + { + std::u32string result; + utf8to32(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u32string utf8to32(const std::u8string_view& s) + { + std::u32string result; + utf8to32(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::size_t find_invalid(const std::u8string& s) + { + std::u8string::const_iterator invalid = find_invalid(s.begin(), s.end()); + return (invalid == s.end()) ? std::string_view::npos + : static_cast(invalid - s.begin()); + } + + inline bool is_valid(const std::u8string& s) + { + return is_valid(s.begin(), s.end()); + } + + inline std::u8string replace_invalid(const std::u8string& s, char32_t replacement) + { + std::u8string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); + return result; + } + + inline std::u8string replace_invalid(const std::u8string& s) + { + std::u8string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline bool starts_with_bom(const std::u8string& s) + { + return starts_with_bom(s.begin(), s.end()); + } + +} // namespace utf8 + +#endif // header guard diff --git a/Include/Pipe/Extern/utf8/unchecked.h b/Include/Pipe/Extern/utf8/unchecked.h index f3cb627f..7546d79c 100644 --- a/Include/Pipe/Extern/utf8/unchecked.h +++ b/Include/Pipe/Extern/utf8/unchecked.h @@ -32,256 +32,273 @@ DEALINGS IN THE SOFTWARE. namespace utf8 { - namespace unchecked - { - template - octet_iterator append(utfchar32_t cp, octet_iterator result) - { - return internal::append(cp, result); - } + namespace unchecked + { + template + octet_iterator append(utfchar32_t cp, octet_iterator result) + { + return internal::append(cp, result); + } - template - word_iterator append16(utfchar32_t cp, word_iterator result) - { - return internal::append16(cp, result); - } + template + word_iterator append16(utfchar32_t cp, word_iterator result) + { + return internal::append16(cp, result); + } - template - output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, utfchar32_t replacement) - { - while (start != end) { - octet_iterator sequence_start = start; - internal::utf_error err_code = utf8::internal::validate_next(start, end); - switch (err_code) { - case internal::UTF8_OK : - for (octet_iterator it = sequence_start; it != start; ++it) - *out++ = *it; - break; - case internal::NOT_ENOUGH_ROOM: - out = utf8::unchecked::append(replacement, out); - start = end; - break; - case internal::INVALID_LEAD: - out = utf8::unchecked::append(replacement, out); - ++start; - break; - case internal::INCOMPLETE_SEQUENCE: - case internal::OVERLONG_SEQUENCE: - case internal::INVALID_CODE_POINT: - out = utf8::unchecked::append(replacement, out); - ++start; - // just one replacement mark for the sequence - while (start != end && utf8::internal::is_trail(*start)) - ++start; - break; - } - } - return out; - } + template + output_iterator replace_invalid( + octet_iterator start, octet_iterator end, output_iterator out, utfchar32_t replacement) + { + while (start != end) + { + octet_iterator sequence_start = start; + internal::utf_error err_code = utf8::internal::validate_next(start, end); + switch (err_code) + { + case internal::UTF8_OK: + for (octet_iterator it = sequence_start; it != start; ++it) + *out++ = *it; + break; + case internal::NOT_ENOUGH_ROOM: + out = utf8::unchecked::append(replacement, out); + start = end; + break; + case internal::INVALID_LEAD: + out = utf8::unchecked::append(replacement, out); + ++start; + break; + case internal::INCOMPLETE_SEQUENCE: + case internal::OVERLONG_SEQUENCE: + case internal::INVALID_CODE_POINT: + out = utf8::unchecked::append(replacement, out); + ++start; + // just one replacement mark for the sequence + while (start != end && utf8::internal::is_trail(*start)) + ++start; + break; + } + } + return out; + } - template - inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) - { - static const utfchar32_t replacement_marker = utf8::internal::mask16(0xfffd); - return utf8::unchecked::replace_invalid(start, end, out, replacement_marker); - } + template + inline output_iterator replace_invalid( + octet_iterator start, octet_iterator end, output_iterator out) + { + static const utfchar32_t replacement_marker = utf8::internal::mask16(0xfffd); + return utf8::unchecked::replace_invalid(start, end, out, replacement_marker); + } - inline std::string replace_invalid(const std::string& s, utfchar32_t replacement) - { - std::string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); - return result; - } + inline std::string replace_invalid(const std::string& s, utfchar32_t replacement) + { + std::string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); + return result; + } - inline std::string replace_invalid(const std::string& s) - { - std::string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result)); - return result; - } + inline std::string replace_invalid(const std::string& s) + { + std::string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result)); + return result; + } - template - utfchar32_t next(octet_iterator& it) - { - utfchar32_t cp = utf8::internal::mask8(*it); - switch (utf8::internal::sequence_length(it)) { - case 1: - break; - case 2: - it++; - cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); - break; - case 3: - ++it; - cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); - ++it; - cp += (*it) & 0x3f; - break; - case 4: - ++it; - cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); - ++it; - cp += (utf8::internal::mask8(*it) << 6) & 0xfff; - ++it; - cp += (*it) & 0x3f; - break; - } - ++it; - return cp; - } + template + utfchar32_t next(octet_iterator& it) + { + utfchar32_t cp = utf8::internal::mask8(*it); + switch (utf8::internal::sequence_length(it)) + { + case 1: break; + case 2: + it++; + cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); + break; + case 3: + ++it; + cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + ++it; + cp += (*it) & 0x3f; + break; + case 4: + ++it; + cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + ++it; + cp += (utf8::internal::mask8(*it) << 6) & 0xfff; + ++it; + cp += (*it) & 0x3f; + break; + } + ++it; + return cp; + } - template - utfchar32_t peek_next(octet_iterator it) - { - return utf8::unchecked::next(it); - } + template + utfchar32_t peek_next(octet_iterator it) + { + return utf8::unchecked::next(it); + } - template - utfchar32_t next16(word_iterator& it) - { - utfchar32_t cp = utf8::internal::mask16(*it++); - if (utf8::internal::is_lead_surrogate(cp)) - return (cp << 10) + *it++ + utf8::internal::SURROGATE_OFFSET; - return cp; - } + template + utfchar32_t next16(word_iterator& it) + { + utfchar32_t cp = utf8::internal::mask16(*it++); + if (utf8::internal::is_lead_surrogate(cp)) + return (cp << 10) + *it++ + utf8::internal::SURROGATE_OFFSET; + return cp; + } - template - utfchar32_t prior(octet_iterator& it) - { - while (utf8::internal::is_trail(*(--it))) ; - octet_iterator temp = it; - return utf8::unchecked::next(temp); - } + template + utfchar32_t prior(octet_iterator& it) + { + while (utf8::internal::is_trail(*(--it))) + ; + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } - template - void advance(octet_iterator& it, distance_type n) - { - const distance_type zero(0); - if (n < zero) { - // backward - for (distance_type i = n; i < zero; ++i) - utf8::unchecked::prior(it); - } else { - // forward - for (distance_type i = zero; i < n; ++i) - utf8::unchecked::next(it); - } - } + template + void advance(octet_iterator& it, distance_type n) + { + const distance_type zero(0); + if (n < zero) + { + // backward + for (distance_type i = n; i < zero; ++i) + utf8::unchecked::prior(it); + } + else + { + // forward + for (distance_type i = zero; i < n; ++i) + utf8::unchecked::next(it); + } + } - template - typename std::iterator_traits::difference_type - distance(octet_iterator first, octet_iterator last) - { - typename std::iterator_traits::difference_type dist; - for (dist = 0; first < last; ++dist) - utf8::unchecked::next(first); - return dist; - } + template + typename std::iterator_traits::difference_type distance( + octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::unchecked::next(first); + return dist; + } - template - octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result) - { - while (start != end) { - utfchar32_t cp = utf8::internal::mask16(*start++); - // Take care of surrogate pairs first - if (utf8::internal::is_lead_surrogate(cp)) { - if (start == end) - return result; - utfchar32_t trail_surrogate = utf8::internal::mask16(*start++); - cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; - } - result = utf8::unchecked::append(cp, result); - } - return result; - } + template + octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) + { + utfchar32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) + { + if (start == end) + return result; + utfchar32_t trail_surrogate = utf8::internal::mask16(*start++); + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + } + result = utf8::unchecked::append(cp, result); + } + return result; + } - template - u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result) - { - while (start < end) { - utfchar32_t cp = utf8::unchecked::next(start); - if (cp > 0xffff) { //make a surrogate pair - *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); - *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); - } - else - *result++ = static_cast(cp); - } - return result; - } + template + u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start < end) + { + utfchar32_t cp = utf8::unchecked::next(start); + if (cp > 0xffff) + { // make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = + static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } - template - octet_iterator utf32to8(u32bit_iterator start, u32bit_iterator end, octet_iterator result) - { - while (start != end) - result = utf8::unchecked::append(*(start++), result); + template + octet_iterator utf32to8(u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::unchecked::append(*(start++), result); - return result; - } + return result; + } - template - u32bit_iterator utf8to32(octet_iterator start, octet_iterator end, u32bit_iterator result) - { - while (start < end) - (*result++) = utf8::unchecked::next(start); + template + u32bit_iterator utf8to32(octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start < end) + (*result++) = utf8::unchecked::next(start); - return result; - } + return result; + } - // The iterator class - template - class iterator { - octet_iterator it; - public: - typedef utfchar32_t value_type; - typedef utfchar32_t* pointer; - typedef utfchar32_t& reference; - typedef std::ptrdiff_t difference_type; - typedef std::bidirectional_iterator_tag iterator_category; - iterator () {} - explicit iterator (const octet_iterator& octet_it): it(octet_it) {} - // the default "big three" are OK - octet_iterator base () const { return it; } - utfchar32_t operator * () const - { - octet_iterator temp = it; - return utf8::unchecked::next(temp); - } - bool operator == (const iterator& rhs) const - { - return (it == rhs.it); - } - bool operator != (const iterator& rhs) const - { - return !(operator == (rhs)); - } - iterator& operator ++ () - { - ::std::advance(it, utf8::internal::sequence_length(it)); - return *this; - } - iterator operator ++ (int) - { - iterator temp = *this; - ::std::advance(it, utf8::internal::sequence_length(it)); - return temp; - } - iterator& operator -- () - { - utf8::unchecked::prior(it); - return *this; - } - iterator operator -- (int) - { - iterator temp = *this; - utf8::unchecked::prior(it); - return temp; - } - }; // class iterator + // The iterator class + template + class iterator + { + octet_iterator it; - } // namespace utf8::unchecked -} // namespace utf8 + public: + typedef utfchar32_t value_type; + typedef utfchar32_t* pointer; + typedef utfchar32_t& reference; + typedef std::ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + iterator() {} + explicit iterator(const octet_iterator& octet_it) : it(octet_it) {} + // the default "big three" are OK + octet_iterator base() const + { + return it; + } + utfchar32_t operator*() const + { + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + bool operator==(const iterator& rhs) const + { + return (it == rhs.it); + } + bool operator!=(const iterator& rhs) const + { + return !(operator==(rhs)); + } + iterator& operator++() + { + ::std::advance(it, utf8::internal::sequence_length(it)); + return *this; + } + iterator operator++(int) + { + iterator temp = *this; + ::std::advance(it, utf8::internal::sequence_length(it)); + return temp; + } + iterator& operator--() + { + utf8::unchecked::prior(it); + return *this; + } + iterator operator--(int) + { + iterator temp = *this; + utf8::unchecked::prior(it); + return temp; + } + }; // class iterator + } // namespace unchecked +} // namespace utf8 -#endif // header guard +#endif // header guard diff --git a/Include/Pipe/Files/Files.h b/Include/Pipe/Files/Files.h index 6231fd56..5bc5ef6e 100644 --- a/Include/Pipe/Files/Files.h +++ b/Include/Pipe/Files/Files.h @@ -12,26 +12,26 @@ namespace p { /** Path API */ - PIPE_API bool ExistsAsFile(const Path& path); - PIPE_API bool ExistsAsFolder(const Path& path); - PIPE_API SpaceInfo Space(const Path& target); + P_API bool ExistsAsFile(const Path& path); + P_API bool ExistsAsFolder(const Path& path); + P_API SpaceInfo Space(const Path& target); /** String API */ - PIPE_API bool CreateFolder(StringView path, bool bRecursive = false); - PIPE_API bool Delete(StringView path, bool bExcludeIfNotEmpty = true, bool bLogErrors = true); + P_API bool CreateFolder(StringView path, bool bRecursive = false); + P_API bool Delete(StringView path, bool bExcludeIfNotEmpty = true, bool bLogErrors = true); // TODO: Implement plural copy and move - PIPE_API bool Copy(StringView origin, StringView destination, + P_API bool Copy(StringView origin, StringView destination, CopyOptions options = CopyOptions::Overwrite | CopyOptions::Recursive); - PIPE_API bool Move(StringView origin, StringView destination); - PIPE_API bool Rename(StringView origin, StringView destination); + P_API bool Move(StringView origin, StringView destination); + P_API bool Rename(StringView origin, StringView destination); - PIPE_API bool LoadStringFile(StringView path, String& result, sizet extraPadding = 0); - PIPE_API bool SaveStringFile(StringView path, StringView data); + P_API bool LoadStringFile(StringView path, String& result, sizet extraPadding = 0); + P_API bool SaveStringFile(StringView path, StringView data); - PIPE_API bool Exists(StringView path); - PIPE_API bool IsFolder(StringView path); - PIPE_API bool IsFile(StringView path); + P_API bool Exists(StringView path); + P_API bool IsFolder(StringView path); + P_API bool IsFile(StringView path); } // namespace p diff --git a/Include/Pipe/Files/Paths.h b/Include/Pipe/Files/Paths.h index 76c48429..6477a326 100644 --- a/Include/Pipe/Files/Paths.h +++ b/Include/Pipe/Files/Paths.h @@ -2,94 +2,96 @@ #pragma once -#include "Pipe/Core/Platform.h" #include "Pipe/Core/String.h" #include "Pipe/Core/StringView.h" #include "Pipe/Export.h" #include "Pipe/Files/STDFileSystem.h" #include "PipeArrays.h" +#include "PipePlatform.h" namespace p { - constexpr TChar separator{'/'}; + namespace details + { + constexpr char separator{'/'}; #if P_PLATFORM_WINDOWS - constexpr TChar preferredSeparator{'\\'}; + constexpr char preferredSeparator{'\\'}; #else - constexpr TChar preferredSeparator{'/'}; + constexpr char preferredSeparator{'/'}; #endif - constexpr TChar dot{'.'}; - constexpr TChar colon{':'}; - + constexpr char dot{'.'}; + constexpr char colon{':'}; + } // namespace details - PIPE_API const TChar* FindRelativeChar(const TChar* const first, const TChar* const last); - PIPE_API const TChar* FindRelativeChar( - const TChar* const first, const TChar* const last, const TChar*& outNameEnd); - PIPE_API const TChar* FindFilename(const TChar* const first, const TChar* last); - PIPE_API const TChar* FindExtension(const TChar* const first, const TChar* last); + P_API const char* FindRelativeChar(const char* const first, const char* const last); + P_API const char* FindRelativeChar( + const char* const first, const char* const last, const char*& outNameEnd); + P_API const char* FindFilename(const char* const first, const char* last); + P_API const char* FindExtension(const char* const first, const char* last); // @return root name of a path, or an empty view if missing // E.g: "C:\Folder" -> "C:" - PIPE_API StringView GetRootPathName(const StringView path); + P_API StringView GetRootPathName(const StringView path); // @return root path of a path, or an empty view if missing // E.g: "C:\Folder" -> "C:\" - PIPE_API StringView GetRootPath(const StringView path); + P_API StringView GetRootPath(const StringView path); // @return the relative path if it exists, otherwise, an empty view // E.g: "C:\Folder\Other" -> "Folder\Other" - PIPE_API StringView GetRelativePath(const StringView path); + P_API StringView GetRelativePath(const StringView path); // @return the path to the parent directory // E.g: "/var/tmp/file.txt" -> "/var/tmp" // E.g: "/var/tmp/." -> "/var/tmp" - PIPE_API StringView GetParentPath(StringView path); + P_API StringView GetParentPath(StringView path); - PIPE_API StringView GetFilename(StringView path); + P_API StringView GetFilename(StringView path); inline StringView GetFilename(const String& path) { return GetFilename(StringView{path}); } - PIPE_API bool HasFilename(StringView path); - PIPE_API void RemoveFilename(String& path); - PIPE_API void RemoveFilename(StringView& path); + P_API bool HasFilename(StringView path); + P_API void RemoveFilename(String& path); + P_API void RemoveFilename(StringView& path); - PIPE_API StringView GetStem(StringView path); - PIPE_API bool HasStem(StringView path); + P_API StringView GetStem(StringView path); + P_API bool HasStem(StringView path); - PIPE_API StringView GetExtension(StringView path); + P_API StringView GetExtension(StringView path); /** Replaces the extension of a path */ - PIPE_API void ReplaceExtension(String& path, StringView newExtension); - PIPE_API bool HasExtension(StringView path); + P_API void ReplaceExtension(String& path, StringView newExtension); + P_API bool HasExtension(StringView path); - PIPE_API bool IsAbsolutePath(StringView path); - PIPE_API bool IsRelativePath(StringView path); - PIPE_API bool IsRemotePath(StringView path); - PIPE_API bool Exists(StringView path); + P_API bool IsAbsolutePath(StringView path); + P_API bool IsRelativePath(StringView path); + P_API bool IsRemotePath(StringView path); + P_API bool Exists(StringView path); - PIPE_API String JoinPaths(StringView base, StringView relative); - PIPE_API String JoinPaths(StringView base, StringView relative, StringView relative2); - PIPE_API String JoinPaths( + P_API String JoinPaths(StringView base, StringView relative); + P_API String JoinPaths(StringView base, StringView relative, StringView relative2); + P_API String JoinPaths( StringView base, StringView relative, StringView relative2, StringView relative3); - PIPE_API String JoinPaths(TView paths); - PIPE_API void AppendToPath(String& base, StringView other); + P_API String JoinPaths(TView paths); + P_API void AppendToPath(String& base, StringView other); - PIPE_API bool AppendPathSeparatorIfNeeded(String& path); + P_API bool AppendPathSeparatorIfNeeded(String& path); - PIPE_API String ToRelativePath(StringView path); - PIPE_API String ToAbsolutePath(StringView path); - PIPE_API String ToRelativePath(StringView path, StringView parent); - PIPE_API String ToAbsolutePath(StringView path, StringView parent); + P_API String ToRelativePath(StringView path); + P_API String ToAbsolutePath(StringView path); + P_API String ToRelativePath(StringView path, StringView parent); + P_API String ToAbsolutePath(StringView path, StringView parent); String LexicallyRelative(StringView path, StringView base); - PIPE_API void SetCanonical(String& path); - PIPE_API void SetWeaklyCanonical(String& path); + P_API void SetCanonical(String& path); + P_API void SetWeaklyCanonical(String& path); - inline PIPE_API constexpr bool IsSeparator(TChar c) + inline P_API constexpr bool IsSeparator(char c) { - return c == separator + return c == details::separator #if P_PLATFORM_WINDOWS - || c == preferredSeparator + || c == details::preferredSeparator #endif ; } @@ -98,17 +100,17 @@ namespace p // a forward slash is the only valid directory separator and also the only valid // element separator. For Windows, forward slash and back slash are the possible // directory separators, but colon (example: "c:foo") is also an element separator. - inline PIPE_API constexpr bool IsElementSeparator(TChar c) + inline P_API constexpr bool IsElementSeparator(char c) { - return c == separator + return c == details::separator #if P_PLATFORM_WINDOWS - || c == preferredSeparator || c == colon + || c == details::preferredSeparator || c == details::colon #endif ; } - PIPE_API String ToString(const Path& path); - PIPE_API std::filesystem::path ToSTDPath(StringView pathStr); + P_API String ToString(const Path& path); + P_API std::filesystem::path ToSTDPath(StringView pathStr); #pragma region PathIterator @@ -142,7 +144,7 @@ namespace p static PathIterator CreateBegin(StringView p) noexcept; static PathIterator CreateEnd(StringView p) noexcept; - const TChar* Peek() const noexcept; + const char* Peek() const noexcept; void Increment() noexcept; void Decrement() noexcept; @@ -162,22 +164,22 @@ namespace p bool InRootPath() const noexcept; private: - void MakeState(State newState, const TChar* start, const TChar* end) noexcept; + void MakeState(State newState, const char* start, const char* end) noexcept; void MakeState(State newState) noexcept; - const TChar* GetAfterBack() const noexcept; - const TChar* GetBeforeFront() const noexcept; + const char* GetAfterBack() const noexcept; + const char* GetBeforeFront() const noexcept; /// @return a pointer to the first character after the currently lexed element. - const TChar* GetNextTokenStartPos() const noexcept; + const char* GetNextTokenStartPos() const noexcept; /// @return a pointer to the first character in the currently lexed element. - const TChar* GetCurrentTokenStartPos() const noexcept; + const char* GetCurrentTokenStartPos() const noexcept; // Consume all consecutive separators - const TChar* ConsumeAllSeparators(const TChar* p, const TChar* end) const noexcept; + const char* ConsumeAllSeparators(const char* p, const char* end) const noexcept; // Consume exactly N separators, or return nullptr. - const TChar* ConsumeNSeparators(const TChar* p, const TChar* end, int N) const noexcept; - const TChar* ConsumeName(const TChar* p, const TChar* end) const noexcept; - const TChar* ConsumeDriveLetter(const TChar* p, const TChar* end) const noexcept; - const TChar* ConsumeNetworkRoot(const TChar* p, const TChar* end) const noexcept; - const TChar* ConsumeRootName(const TChar* p, const TChar* end) const noexcept; + const char* ConsumeNSeparators(const char* p, const char* end, int N) const noexcept; + const char* ConsumeName(const char* p, const char* end) const noexcept; + const char* ConsumeDriveLetter(const char* p, const char* end) const noexcept; + const char* ConsumeNetworkRoot(const char* p, const char* end) const noexcept; + const char* ConsumeRootName(const char* p, const char* end) const noexcept; }; #pragma endregion PathIterator } // namespace p diff --git a/Include/Pipe/Files/PlatformPaths.h b/Include/Pipe/Files/PlatformPaths.h index de2382c9..6ea13bab 100644 --- a/Include/Pipe/Files/PlatformPaths.h +++ b/Include/Pipe/Files/PlatformPaths.h @@ -2,9 +2,9 @@ #pragma once -#include "Pipe/Core/Platform.h" #include "Pipe/Core/StringView.h" #include "Pipe/Export.h" +#include "PipePlatform.h" #if P_PLATFORM_LINUX @@ -14,7 +14,7 @@ namespace p { - struct PIPE_API GenericPlatformPaths + struct P_API GenericPlatformPaths { static constexpr u32 GetMaxPathLength() { @@ -45,7 +45,7 @@ namespace p #if P_PLATFORM_WINDOWS - struct PIPE_API WindowsPlatformPaths : public GenericPlatformPaths + struct P_API WindowsPlatformPaths : public GenericPlatformPaths { static u32 GetMaxPathLength(); @@ -66,7 +66,7 @@ namespace p using PlatformPaths = WindowsPlatformPaths; #elif P_PLATFORM_LINUX - struct PIPE_API LinuxPlatformPaths : public GenericPlatformPaths + struct P_API LinuxPlatformPaths : public GenericPlatformPaths { static constexpr u32 GetMaxPathLength() { @@ -91,7 +91,7 @@ namespace p using PlatformPaths = LinuxPlatformPaths; #elif P_PLATFORM_MACOS - struct PIPE_API MacPlatformPaths : public GenericPlatformPaths + struct P_API MacPlatformPaths : public GenericPlatformPaths { static constexpr u32 GetMaxPathLength() { diff --git a/Include/Pipe/Files/STDFileSystem.h b/Include/Pipe/Files/STDFileSystem.h index 337a8ae3..0cd8030a 100644 --- a/Include/Pipe/Files/STDFileSystem.h +++ b/Include/Pipe/Files/STDFileSystem.h @@ -4,8 +4,8 @@ #include "Pipe/Core/EnumFlags.h" #include "Pipe/Core/Hash.h" -#include "Pipe/Core/Platform.h" #include "Pipe/Core/StringView.h" +#include "PipePlatform.h" #include "PipeSerializeFwd.h" #include @@ -51,9 +51,9 @@ namespace p return GetStringHash(path.c_str()); } - PIPE_API void Read(p::Reader& ct, p::Path& value); - PIPE_API void Write(p::Writer& ct, const p::Path& value); + P_API void Read(p::Reader& ct, p::Path& value); + P_API void Write(p::Writer& ct, const p::Path& value); - PIPE_API DirectoryIterator CreateDirIterator(StringView path); - PIPE_API RecursiveDirectoryIterator CreateRecursiveDirIterator(StringView path); + P_API DirectoryIterator CreateDirIterator(StringView path); + P_API RecursiveDirectoryIterator CreateRecursiveDirIterator(StringView path); } // namespace p diff --git a/Include/Pipe/Memory/Alloc.h b/Include/Pipe/Memory/Alloc.h deleted file mode 100644 index e3cebdab..00000000 --- a/Include/Pipe/Memory/Alloc.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/Platform.h" -#include "Pipe/Core/TypeTraits.h" -#include "Pipe/Export.h" - - -namespace p -{ - class Arena; - - void InitializeMemory(); - - // Native allocation functions - PIPE_API void* HeapAlloc(sizet size); - PIPE_API void* HeapAlloc(sizet size, sizet align); - PIPE_API void* HeapRealloc(void* ptr, sizet size); - PIPE_API void HeapFree(void* ptr); - - - PIPE_API class HeapArena& GetHeapArena(); - PIPE_API Arena& GetCurrentArena(); - PIPE_API void SetCurrentArena(Arena& arena); - - // Arena allocation functions (Find current arena) - PIPE_API void* Alloc(sizet size); - PIPE_API void* Alloc(sizet size, sizet align); - PIPE_API bool Realloc(void* ptr, sizet ptrSize, sizet size); - PIPE_API void Free(void* ptr, sizet size); - - - // Templated arena allocation functions: - - template ArenaT> - T* Alloc(ArenaT& arena, sizet count = 1) requires(!IsVoid) - { - return static_cast(arena.Alloc(sizeof(T) * count, alignof(T))); - } - template ArenaT> - T* Alloc(ArenaT& arena, sizet count, sizet align) requires(!IsVoid) - { - return static_cast(arena.Alloc(sizeof(T) * count, align)); - } - template ArenaT> - bool Realloc(ArenaT& arena, T* ptr, sizet ptrCount, sizet newCount) requires(!IsVoid) - { - return arena.Realloc(ptr, sizeof(T) * ptrCount, sizeof(T) * newCount); - } - template ArenaT> - void Free(ArenaT& arena, T* ptr, u32 count = 1) requires(!IsVoid) - { - arena.Free(static_cast(ptr), sizeof(T) * count); - } -} // namespace p diff --git a/Include/Pipe/Memory/Arena.h b/Include/Pipe/Memory/Arena.h deleted file mode 100644 index c0c20d0e..00000000 --- a/Include/Pipe/Memory/Arena.h +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/Platform.h" -#include "Pipe/Core/TypeTraits.h" -#include "Pipe/Memory/Block.h" - - -namespace p -{ - template - struct TInlineArray; - - - /** Arena defines the API used on all other arena types */ - class PIPE_API Arena - { - public: - using AllocSignature = void*(Arena*, sizet size); - using AllocAlignedSignature = void*(Arena*, sizet size, sizet align); - using ResizeSignature = bool(Arena*, void* ptr, sizet ptrSize, sizet size); - using FreeSignature = void(Arena*, void* ptr, sizet size); - - private: - AllocSignature* doAlloc = nullptr; - AllocAlignedSignature* doAllocAligned = nullptr; - ResizeSignature* doRealloc = nullptr; - FreeSignature* doFree = nullptr; - - - template - static consteval bool ImplementsAlloc() - { -#if defined(__GNUC__) // GCC won't detect this check as constexpr! :( - return true; -#else - return (void* (T::*)(sizet size))(&T::Alloc) - != (void* (T::*)(sizet size))(&Arena::Alloc); -#endif - } - template - static consteval bool ImplementsAllocAligned() - { -#if defined(__GNUC__) // GCC won't detect this check as constexpr! :( - return true; -#else - return (void* (T::*)(sizet size, sizet align))(&T::Alloc) - != (void* (T::*)(sizet size, sizet align))(&Arena::Alloc); -#endif - } - template - static consteval bool ImplementsRealloc() - { -#if defined(__GNUC__) // GCC won't detect this check as constexpr! :( - return true; -#else - return &T::Realloc != &Arena::Realloc; -#endif - } - template - static consteval bool ImplementsFree() - { -#if defined(__GNUC__) // GCC won't detect this check as constexpr! :( - return true; -#else - return &T::Free != &Arena::Free; -#endif - } - - protected: - - template T> - void Interface() - { - doAlloc = [](Arena* self, sizet size) { - static_assert(ImplementsAlloc() && "Alloc is not implemented"); - return static_cast(self)->Alloc(size); - }; - doAllocAligned = [](Arena* self, sizet size, sizet align) { - static_assert(ImplementsAllocAligned() && "Alloc (aligned) is not implemented"); - return static_cast(self)->Alloc(size, align); - }; - doRealloc = [](Arena* self, void* ptr, sizet ptrSize, sizet size) { - static_assert(ImplementsRealloc() && "Realloc is not implemented"); - return static_cast(self)->Realloc(ptr, ptrSize, size); - }; - doFree = [](Arena* self, void* ptr, sizet size) { - static_assert(ImplementsFree() && "Free is not implemented"); - return static_cast(self)->Free(ptr, size); - }; - } - - public: - Arena() = default; - virtual ~Arena() {} - Arena(const Arena&) = delete; - Arena& operator=(const Arena&) = delete; - - Arena(Arena&&) = default; - Arena& operator=(Arena&&) = default; - - - void* Alloc(sizet size) - { - return doAlloc(this, size); - } - void* Alloc(sizet size, sizet align) - { - return doAllocAligned(this, size, align); - } - bool Realloc(void* ptr, sizet ptrSize, sizet size) - { - return doRealloc(this, ptr, ptrSize, size); - } - void Free(void* ptr, sizet size) - { - doFree(this, ptr, size); - } - - virtual sizet GetAvailableMemory() const - { - return 0; - } - virtual void GetBlocks(TInlineArray& outBlocks) const {} - - virtual const struct MemoryStats* GetStats() const { return nullptr; } - }; - - class PIPE_API ChildArena : public Arena - { - protected: - Arena* parent = nullptr; - - public: - ChildArena(Arena* parent); - - Arena& GetParentArena() const - { - return *parent; - } - }; -} // namespace p diff --git a/Include/Pipe/Memory/BestFitArena.h b/Include/Pipe/Memory/BestFitArena.h deleted file mode 100644 index 3f4c8c4f..00000000 --- a/Include/Pipe/Memory/BestFitArena.h +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Memory/Arena.h" -#include "Pipe/Memory/Block.h" -#include "Pipe/Memory/Memory.h" -#include "PipeArrays.h" - - -namespace p -{ - class PIPE_API BestFitArena : public ChildArena - { - public: - struct Slot - { - u8* start; - sizet size; - - u8* End() const; - - auto operator==(const Slot& other) const - { - return size == other.size; - } - auto operator<(const Slot& other) const - { - return size < other.size; - } - auto operator>(const Slot& other) const - { - return size > other.size; - } - auto operator<=(const Slot& other) const - { - return size <= other.size; - } - auto operator>=(const Slot& other) const - { - return size >= other.size; - } - }; - - protected: - // TODO: Support growing multiple blocks - Memory::Block block{}; - TArray freeSlots{}; - bool pendingSort = false; - sizet freeSize = 0; - - - public: - BestFitArena(Arena* parent, const sizet initialSize = 4 * Memory::KB); - BestFitArena(const sizet initialSize = 4 * Memory::KB) : BestFitArena(nullptr, initialSize) - {} - ~BestFitArena() override; - - void* Alloc(const sizet size); - void* Alloc(const sizet size, sizet align); - bool Realloc(void* ptr, const sizet ptrSize, const sizet size) - { - return false; - } - void Free(void* ptr, sizet size); - - const Memory::Block& GetBlock() const - { - return block; - } - - bool Contains(void* ptr) - { - return block.Contains(ptr); - } - sizet GetFreeSize() - { - return freeSize; - } - - sizet GetUsedSize() - { - return block.size - freeSize; - } - - const TArray& GetFreeSlots() const - { - return freeSlots; - } - - private: - i32 FindSmallestSlot(sizet neededSize); - void ReduceSlot( - i32 slotIndex, Slot& slot, u8* const allocationStart, u8* const allocationEnd); - void AbsorbFreeSpace(u8* const allocationStart, u8* const allocationEnd); - }; - - - inline u8* BestFitArena::Slot::End() const - { - return start + size; - } -} // namespace p diff --git a/Include/Pipe/Memory/BigBestFitArena.h b/Include/Pipe/Memory/BigBestFitArena.h deleted file mode 100644 index b9bc72f1..00000000 --- a/Include/Pipe/Memory/BigBestFitArena.h +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Memory/Arena.h" -#include "Pipe/Memory/BigBestFitArena.h" -#include "Pipe/Memory/Block.h" -#include "PipeArrays.h" - - -namespace p -{ - class PIPE_API BigBestFitArena : public ChildArena - { - public: - struct AllocationHeader - { - u8* end; - }; - - struct PIPE_API Slot - { - u32 offset; - u32 size; - - bool operator==(const Slot& other) const - { - return size == other.size; - } - auto operator<=>(const Slot& other) const - { - return size <=> other.size; - } - }; - - protected: - static constexpr sizet minAlignment = sizeof(AllocationHeader); - // TODO: Support growing multiple blocks - Memory::Block block{}; - TArray freeSlots{}; - bool pendingSort = false; - sizet freeSize = 0; - - - public: - BigBestFitArena(Arena* parent, const sizet initialSize = 1024); - BigBestFitArena(const sizet initialSize = 1024) : BigBestFitArena(nullptr, initialSize) {} - ~BigBestFitArena() override; - - void* Alloc(const sizet size); - void* Alloc(const sizet size, sizet alignment); - bool Realloc(void* ptr, sizet ptrSize, sizet size) - { - return false; - } - void Free(void* ptr, sizet size); - - const Memory::Block& GetBlock() const - { - return block; - } - - bool Contains(void* ptr) - { - return block.Contains(ptr); - } - sizet GetFreeSize() - { - return freeSize; - } - - sizet GetUsedSize() - { - return block.size - freeSize; - } - - const TArray& GetFreeSlots() const - { - return freeSlots; - } - - void* GetAllocationStart(void* ptr) const - { - return GetHeader(ptr); - } - void* GetAllocationEnd(void* ptr) const - { - return GetHeader(ptr)->end; - } - - private: - AllocationHeader* GetHeader(void* ptr) const - { - return reinterpret_cast( - static_cast(ptr) - sizeof(AllocationHeader)); - } - - i32 FindSmallestSlot(sizet size); - void ReduceSlot(i32 slotIndex, Slot& slot, u32 allocationStart, u32 allocationEnd); - void AbsorbFreeSpace(u32 allocationStart, u32 allocationEnd); - - u32 ToOffset(void* data, void* block); - }; - - - /*inline auto operator<=>(const BigBestFitArena::Slot& one, const BigBestFitArena::Slot& other) - { - return sizet(one.end - one.start) <=> sizet(other.end - other.start); - } - inline auto operator<=>(const BigBestFitArena::Slot& one, sizet other) - { - return sizet(one.end - one.start) <=> other; - } - inline auto operator<=>(sizet one, const BigBestFitArena::Slot& other) - { - return one <=> sizet(other.end - other.start); - } - - // Operator <=> doesnt implement == in this case - inline auto operator==(const BigBestFitArena::Slot& one, const BigBestFitArena::Slot& other) - { - return sizet(one.end - one.start) == sizet(other.end - other.start); - } - inline auto operator==(const BigBestFitArena::Slot& one, sizet other) - { - return sizet(one.end - one.start) == other; - }*/ -} // namespace p diff --git a/Include/Pipe/Memory/Block.h b/Include/Pipe/Memory/Block.h deleted file mode 100644 index bb2c111f..00000000 --- a/Include/Pipe/Memory/Block.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/Platform.h" -#include "Pipe/Export.h" - - -namespace p::Memory -{ - struct PIPE_API Block - { - void* data = nullptr; - sizet size = 0; - - - Block() = default; - Block(void* data, sizet size) : data{data}, size{size} {} - Block(Block&& other) noexcept; - Block& operator=(Block&& other) noexcept; - Block(const Block& other) = default; - Block& operator=(const Block& other) = default; - - - bool IsAllocated() const - { - return data != nullptr; - } - - void* Begin() const - { - return data; - } - void* End() const - { - return static_cast(data) + size; - } - - bool Contains(void* ptr) const - { - return data <= ptr && static_cast(data) + size > ptr; - } - - const void* operator*() const - { - return data; - } - - void* operator*() - { - return data; - } - }; -} // namespace p::Memory diff --git a/Include/Pipe/Memory/DummyArena.h b/Include/Pipe/Memory/DummyArena.h deleted file mode 100644 index 4d6af13b..00000000 --- a/Include/Pipe/Memory/DummyArena.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -namespace p -{ - /** This is an Arena interface. - * It serves the single purpose of documenting an Arena's interface. - * Not intended to be used. - */ - class PIPE_API DummyArena - { - public: - ~DummyArena() {} - - // Define copy or move as desired - DummyArena(const DummyArena&) = default; - DummyArena(DummyArena&&) = default; - DummyArena& operator=(const DummyArena&) = default; - DummyArena& operator=(DummyArena&&) = default; - - void* Alloc(const sizet size) - { - return nullptr; - } - void* Alloc(const sizet size, const sizet alignment) - { - return nullptr; - } - bool Realloc(void* ptr, const sizet ptrSize, const sizet size) - { - return false; - } - void Free(void* ptr) {} - }; -} // namespace p diff --git a/Include/Pipe/Memory/HeapArena.h b/Include/Pipe/Memory/HeapArena.h deleted file mode 100644 index 6619c2a3..00000000 --- a/Include/Pipe/Memory/HeapArena.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Memory/Alloc.h" -#include "Pipe/Memory/Arena.h" - -#include "Pipe/Memory/MemoryStats.h" - - -namespace p -{ - class PIPE_API HeapArena : public Arena - { - private: - MemoryStats stats; - - public: - HeapArena() - { - stats.name = "Heap Arena"; - Interface(); - } - ~HeapArena() override = default; - - void* Alloc(const sizet size) - { - void* ptr = p::HeapAlloc(size); - stats.Add(ptr, size); - return ptr; - } - void* Alloc(const sizet size, const sizet align) - { - void* ptr = p::HeapAlloc(size, align); - stats.Add(ptr, size); - return ptr; - } - bool Realloc(void* ptr, const sizet ptrSize, const sizet size) - { - return false; - } - void Free(void* ptr, sizet size) - { - stats.Remove(ptr, size); - p::HeapFree(ptr); - } - - const MemoryStats* GetStats() const override { return &stats; } - }; -} // namespace p diff --git a/Include/Pipe/Memory/Memory.h b/Include/Pipe/Memory/Memory.h deleted file mode 100644 index db88df6c..00000000 --- a/Include/Pipe/Memory/Memory.h +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/Platform.h" -#include "Pipe/Core/TypeTraits.h" -#include "Pipe/Core/Utility.h" -#include "Pipe/Export.h" - - -namespace p -{ - namespace Internal - { - template - struct TIsCharacterByteOrBool : std::false_type - {}; - template<> - struct TIsCharacterByteOrBool : std::true_type - {}; // chars are characters - template<> - struct TIsCharacterByteOrBool : std::true_type - {}; // signed chars are also characters - template<> - struct TIsCharacterByteOrBool : std::true_type - {}; // unsigned chars are also characters - template<> - struct TIsCharacterByteOrBool : std::true_type - {}; - template<> - struct TIsCharacterByteOrBool : std::true_type - {}; - template<> - struct TIsCharacterByteOrBool : std::true_type - {}; - - template - struct TIsMemsetZeroConstructible - { - static constexpr bool value = - IsScalar && TIsCharacterByteOrBool::value && !IsVolatile; - }; - - template - struct TIsMemsetValueConstructible - { - static constexpr bool value = IsScalar && !IsVolatile && !IsMemberPointer; - }; - - template - bool AreBitsZero(const T& value) requires(IsScalar && !IsMemberPointer) - { - if constexpr (IsSame) - { - return true; - } - else - { - constexpr T zero{}; - return CmpMem(&value, &zero, sizeof(T)) == 0; - } - } - } // namespace Internal - - namespace Memory - { - // Kilobyte, Megabyte, Gigabyte... - constexpr sizet B = 1; - constexpr sizet KB = 1024; - constexpr sizet MB = KB * 1024; - constexpr sizet GB = MB * 1024; - constexpr sizet TB = GB * 1024; - constexpr sizet PB = TB * 1024; - - // Kilobit, Megabit, Gigabit... - constexpr sizet Kb = KB / 8; - constexpr sizet Mb = MB / 8; - constexpr sizet Gb = GB / 8; - constexpr sizet Tb = TB / 8; - constexpr sizet Pb = PB / 8; - } // namespace Memory - - - PIPE_API void MoveMem(void* dest, void* src, sizet size); - PIPE_API void CopyMem(void* dest, const void* src, sizet size); - PIPE_API void SwapMem(void* a, void* b, sizet size); - PIPE_API void SetMem(void* dest, u8 value, sizet size); - PIPE_API void SetZeroMem(void* dest, sizet size); - PIPE_API i32 CmpMem(const void* a, const void* b, sizet size); - - /** - * @return the previous pointer of 'ptr' aligned to blockSize - */ - void* GetAlignedBlock(void* ptr, const sizet blockSize); - - /** - * @return the number of bytes needed for p to be aligned in 'align' - */ - PIPE_API sizet GetAlignmentPadding(const void* p, sizet align); - - PIPE_API sizet GetAlignmentPaddingWithHeader(const void* ptr, sizet align, sizet headerSize); - - - /** Constructs a number of contiguous items with the default constructor */ - template - constexpr void ConstructItems(T* data, sizet count) - { - if (!std::is_constant_evaluated() && Internal::TIsMemsetZeroConstructible::value) - { - SetMem(data, 0, count * sizeof(T)); - } - else - { - const T* const end = data + count; - while (data < end) - { - new (data) T(); - ++data; - } - } - } - - /** Constructs a number of contiguous items by copying from a single source item */ - template - constexpr void ConstructItems(T* data, sizet count, const T& value) - { - if (!std::is_constant_evaluated()) - { - if constexpr (Internal::TIsMemsetValueConstructible::value) - { - SetMem(data, static_cast(value), count * sizeof(T)); - return; - } - else if constexpr (Internal::TIsMemsetZeroConstructible::value) - { - if (Internal::AreBitsZero(value)) - { - SetMem(data, 0, count * sizeof(T)); - return; - } - } - } - - const T* const end = data + count; - while (data < end) - { - new (data) T(value); - ++data; - } - } - - /** Constructs a number of contiguous items by copying from source items */ - template - constexpr void CopyConstructItems(T* data, sizet count, const T* values) - { - if (!std::is_constant_evaluated() && IsTriviallyCopyConstructible) - { - CopyMem(data, values, count * sizeof(T)); - } - else - { - const T* const end = values + count; - while (values < end) - { - new (data) T(*values); - if constexpr (destroySourceInPlace) - { - values->T::~T(); - } - ++values; - ++data; - } - } - } - - template - constexpr void MoveConstructItems(T* data, sizet count, T* values) - { - if (!std::is_constant_evaluated() && IsTriviallyMoveConstructible) - { - MoveMem(data, values, count * sizeof(T)); - } - else - { - const T* const end = values + count; - while (values < end) - { - new (data) T(p::Forward(*values)); - if constexpr (destroySourceInPlace) - { - values->T::~T(); - } - ++values; - ++data; - } - } - } - - template - constexpr void MoveOrCopyConstructItems(T* data, sizet count, T* values) - { - if constexpr (IsMoveConstructible || !IsCopyConstructible) - { - MoveConstructItems(data, count, values); - } - else - { - CopyConstructItems(data, count, values); - } - } - - template - constexpr void CopyItems(T* dest, sizet count, const T* src) - { - if (!std::is_constant_evaluated() && IsTriviallyCopyAssignable) - { - CopyMem(dest, src, count * sizeof(T)); - } - else - { - const T* const end = src + count; - while (src < end) - { - *dest = *src; - if constexpr (destroySourceInPlace) - { - src->T::~T(); - } - ++src; - ++dest; - } - } - } - - template - constexpr void MoveItems(T* dest, sizet count, T* source) - { - if (!std::is_constant_evaluated() && IsTriviallyMoveAssignable) - { - MoveMem(dest, source, count * sizeof(T)); - } - else - { - const T* const end = source + count; - while (source < end) - { - *dest = p::Forward(*source); - if constexpr (destroySourceInPlace) - { - source->T::~T(); - } - ++source; - ++dest; - } - } - } - - // Move items [source, source + count) backwards to [..., dest) - template - void MoveItemsBackwards(T* dest, sizet count, T* source) - { - if (!std::is_constant_evaluated() && IsTriviallyMoveAssignable) - { - // MoveMem (or std::memmove) is safe in overlapping memory - p::MoveMem(dest, source, count * sizeof(T)); - } - else - { - T* lastDest = dest + count; - T* lastSource = source + count; - while (lastSource != source) - { - *--lastDest = p::Forward(*--lastSource); - } - } - } - - template - constexpr void DestroyItems(T* data, sizet count) - { - if (!std::is_constant_evaluated() && IsTriviallyDestructible) - { - // Do nothing. No destruction needed. - } - else - { - const T* const end = data + count; - while (data < end) - { - data->T::~T(); - ++data; - } - } - } -} // namespace p diff --git a/Include/Pipe/Memory/MemoryStats.h b/Include/Pipe/Memory/MemoryStats.h index 594d654b..5e5dd3ee 100644 --- a/Include/Pipe/Memory/MemoryStats.h +++ b/Include/Pipe/Memory/MemoryStats.h @@ -2,13 +2,9 @@ #pragma once -#include "Pipe/Core/Platform.h" #include "Pipe/Core/StringView.h" #include "PipeArrays.h" - -// #if P_ENABLE_ALLOCATION_STACKS -#include "Pipe/Core/Backward.h" -// #endif +#include "PipePlatform.h" #include #include @@ -16,53 +12,47 @@ namespace p { - struct PIPE_API AllocationStats + struct P_API AllocationStats { u8* ptr = nullptr; u64 size = 0; }; - struct PIPE_API SortLessAllocationStats - { - bool operator()(const AllocationStats& a, const AllocationStats& b) const - { - return a.ptr + a.size < b.ptr; - } + struct P_API SortLessAllocationStats{bool operator()( + const AllocationStats& a, const AllocationStats& b) const {return a.ptr + a.size < b.ptr; +} // namespace p - bool operator()(void* a, const AllocationStats& b) const - { - return a < b.ptr; - } +bool operator()(void* a, const AllocationStats& b) const +{ + return a < b.ptr; +} - bool operator()(const AllocationStats& a, void* b) const - { - return a.ptr + a.size < b; - } - }; +bool operator()(const AllocationStats& a, void* b) const +{ + return a.ptr + a.size < b; +} +} +; - struct PIPE_API MemoryStats - { - const char* name = "Arena"; +struct P_API MemoryStats +{ + const char* name = "Arena"; - sizet used = 0; - sizet available = 0; - mutable std::shared_mutex mutex; - TArray allocations; -#if P_ENABLE_ALLOCATION_STACKS - TArray stacks; -#endif - TArray freedAllocations; + sizet used = 0; + sizet available = 0; + mutable std::shared_mutex mutex; + TArray allocations; + TArray freedAllocations; - MemoryStats(); - ~MemoryStats(); + MemoryStats(); + ~MemoryStats(); - void Add(void* ptr, sizet size); - void Remove(void* ptr, sizet size); - void Release(); + void Add(void* ptr, sizet size); + void Remove(void* ptr, sizet size); + void Release(); - private: - void PrintAllocationError( - StringView error, AllocationStats* allocation, const backward::StackTrace* stack); - }; +private: + void PrintAllocationError(StringView error, AllocationStats* allocation); +}; } // namespace p diff --git a/Include/Pipe/Memory/MonoLinearArena.h b/Include/Pipe/Memory/MonoLinearArena.h deleted file mode 100644 index 7b716e76..00000000 --- a/Include/Pipe/Memory/MonoLinearArena.h +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/Limits.h" -#include "Pipe/Core/Utility.h" -#include "Pipe/Memory/Alloc.h" -#include "Pipe/Memory/Arena.h" -#include "Pipe/Memory/Block.h" -#include "Pipe/Memory/Memory.h" -#include "PipeArrays.h" -#include "PipeMath.h" - -#include "Pipe/Memory/MemoryStats.h" - - -namespace p -{ - /** - * LinearArena holds memory linearly in a block of memory. - * (Sometimes called ZoneArena if it resizes) - * Individual allocations can't be freed. It can - * be resized, but never smaller than its used size. - */ - class PIPE_API MonoLinearArena : public ChildArena - { -private: - MemoryStats stats; - - protected: - void* insert = nullptr; - sizet count = 0; - Memory::Block block{}; - bool selfAllocated = false; - - - public: - MonoLinearArena(Memory::Block externalBlock, Arena& parentArena = GetCurrentArena()) - : ChildArena(&parentArena), insert{externalBlock.data}, block{Move(externalBlock)} - { - stats.name = "Mono Linear Arena"; - Interface(); - } - MonoLinearArena(const sizet blockSize = Memory::MB, Arena& parentArena = GetCurrentArena()) - : ChildArena(&parentArena) - , insert{GetParentArena().Alloc(blockSize)} - , block{insert, blockSize} - , selfAllocated{true} - { - stats.name = "Mono Linear Arena"; - Interface(); - } - ~MonoLinearArena() override - { - Release(false); - } - - void* Alloc(sizet size) - { - // TODO: Resolve a reasonable align size based on allocation size - return Alloc(size, alignof(std::max_align_t)); - } - void* Alloc(sizet size, sizet align); - bool Realloc(void* ptr, sizet ptrSize, sizet size) - { - return false; - } - void Free(void* ptr, sizet size); - - void Release(bool keepIfSelfAllocated = true); - - sizet GetAvailableMemory() const override - { - return block.size; - } - void GetBlocks(TArray& outBlocks) const override - { - outBlocks.Add(block); - } - - const MemoryStats* GetStats() const override { return &stats; } - }; - - // TMonoLinearArena works like a MonoLinearArena but providing a block on the stack as the block - // to use - template - class PIPE_API TMonoLinearArena : public MonoLinearArena - { - u8 buffer[blockSize]; - - - TMonoLinearArena(Arena& parentArena = GetCurrentArena()) - : MonoLinearArena(Memory::Block{buffer, blockSize}, parentArena) - {} - }; -} // namespace p diff --git a/Include/Pipe/Memory/MultiLinearArena.h b/Include/Pipe/Memory/MultiLinearArena.h deleted file mode 100644 index 5612ff49..00000000 --- a/Include/Pipe/Memory/MultiLinearArena.h +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/Checks.h" -#include "Pipe/Memory/Alloc.h" -#include "Pipe/Memory/Arena.h" -#include "Pipe/Memory/Memory.h" - - -namespace p -{ - struct PIPE_API LinearBlock - { - sizet count = 0; // Counts number of live allocations - LinearBlock* next = nullptr; - LinearBlock* last = nullptr; - void* unaligned = nullptr; // Pointer where this block was allocated before alignment - - constexpr u8* End(sizet blockSize) const - { - return (u8*)unaligned + blockSize + sizeof(LinearBlock); - } - }; - - template - struct PIPE_API LinearBasePool - { - void* insert = nullptr; // Pointer where to insert new allocations - LinearBlock* freeBlock = nullptr; - - - private: - void AllocateBlock(Arena& parentArena); - void FreeBlock(Arena& parentArena, LinearBlock* block); - - public: - void* Alloc(Arena& parentArena, sizet size, sizet align); - void Free(Arena& parentArena, void* ptr, sizet size); - - void Release(Arena& parentArena); - - static constexpr sizet GetBlockSize() - { - return blockSize; - } - - static constexpr sizet GetAllocatedBlockSize() - { - return blockSize + sizeof(LinearBlock); - } - - void* GetBlockEnd(LinearBlock* block) const - { - return (u8*)block->unaligned + GetAllocatedBlockSize(); - } - }; - - struct PIPE_API LinearSmallPool : public LinearBasePool<1 * Memory::MB> - { - static constexpr sizet minSize = 0; - static constexpr sizet maxSize = 8 * Memory::KB; - }; - - struct PIPE_API LinearMediumPool : public LinearBasePool<4 * Memory::MB> - { - static constexpr sizet minSize = LinearSmallPool::maxSize; - static constexpr sizet maxSize = 512 * Memory::KB; - }; - - struct PIPE_API LinearBigPool : public LinearBasePool<16 * Memory::MB> - { - static constexpr sizet minSize = LinearMediumPool::maxSize; - static constexpr sizet maxSize = 4 * Memory::MB; - // Block size is the size of the allocation - }; - - - /** - * LinearArena holds memory linearly in a block of memory. - * (Sometimes called ZoneArena if it resizes) - * Individual allocations can't be freed. It can - * be resized, but never smaller than its used size. - */ - class PIPE_API MultiLinearArena : public ChildArena - { - protected: - LinearSmallPool smallPool; - LinearMediumPool mediumPool; - LinearBigPool bigPool; - - - public: - MultiLinearArena(Arena& parentArena = GetCurrentArena()) : ChildArena(&parentArena) - { - Interface(); - } - ~MultiLinearArena() override - { - Release(); - } - - void* Alloc(sizet size) - { - // TODO: Resolve a reasonable align size based on allocation size - return Alloc(size, alignof(std::max_align_t)); - } - void* Alloc(sizet size, sizet align); - bool Realloc(void* ptr, sizet ptrSize, sizet size) - { - return false; - } - void Free(void* ptr, sizet size); - - - void Release(); - - void Grow(sizet size, sizet align = 0); - }; -} // namespace p diff --git a/Include/Pipe/Memory/OwnPtr.h b/Include/Pipe/Memory/OwnPtr.h index 28904d73..d6afb039 100644 --- a/Include/Pipe/Memory/OwnPtr.h +++ b/Include/Pipe/Memory/OwnPtr.h @@ -6,9 +6,8 @@ #include "Pipe/Core/TypeId.h" #include "Pipe/Core/TypeTraits.h" #include "Pipe/Core/Utility.h" -#include "Pipe/Memory/Alloc.h" -#include "Pipe/Memory/Arena.h" #include "Pipe/Memory/PtrBuilder.h" +#include "PipeMemory.h" #include @@ -42,7 +41,7 @@ namespace p } // namespace Internal - struct PIPE_API BaseOwnPtr + struct P_API BaseOwnPtr { friend struct Ptr; @@ -54,12 +53,12 @@ namespace p public: BaseOwnPtr(BaseOwnPtr&& other) noexcept { - MoveFrom(Move(other)); + MoveFromUnsafe(Move(other)); } BaseOwnPtr& operator=(BaseOwnPtr&& other) noexcept { Delete(); - MoveFrom(Move(other)); + MoveFromUnsafe(Move(other)); return *this; } ~BaseOwnPtr() @@ -93,6 +92,14 @@ namespace p return value; } + void MoveFromUnsafe(BaseOwnPtr&& other) + { + value = other.value; + counter = other.counter; + other.value = nullptr; + other.counter = nullptr; + } + protected: BaseOwnPtr() = default; // Initialization from parent @@ -104,19 +111,10 @@ namespace p Internal::PtrWeakCounter(arena, deleter); } } - - - void MoveFrom(BaseOwnPtr&& other) - { - value = other.value; - counter = other.counter; - other.value = nullptr; - other.counter = nullptr; - } }; - struct PIPE_API Ptr + struct P_API Ptr { protected: void* value = nullptr; @@ -165,9 +163,8 @@ namespace p return value; } - protected: - void MoveFrom(Ptr&& other); - void CopyFrom(const Ptr& other); + void MoveFromUnsafe(Ptr&& other); + void CopyFromUnsafe(const Ptr& other); private: void ResetNoCheck(const bool bIsSet); @@ -211,14 +208,14 @@ namespace p TOwnPtr(TOwnPtr&& other) noexcept { - MoveFrom(Move(other)); + MoveFromUnsafe(Move(other)); } TOwnPtr(OwnPtr&& other) noexcept; TOwnPtr& operator=(TOwnPtr&& other) noexcept { Delete(); - MoveFrom(Move(other)); + MoveFromUnsafe(Move(other)); return *this; } @@ -228,22 +225,17 @@ namespace p template TOwnPtr(TOwnPtr&& other) noexcept requires Derived { - MoveFrom(Move(other)); + MoveFromUnsafe(Move(other)); } template TOwnPtr& operator=(TOwnPtr&& other) noexcept requires Derived { Delete(); - MoveFrom(Move(other)); + MoveFromUnsafe(Move(other)); return *this; } T* Get() const - { - return IsValid() ? GetUnsafe() : nullptr; - } - - T* GetUnsafe() const { return static_cast(value); } @@ -254,28 +246,15 @@ namespace p template TOwnPtr Transfer() { - // If can be casted statically or dynamically - if (IsValid() && (Convertible || dynamic_cast(GetUnsafe()) != nullptr)) + if (Cast(Get())) { TOwnPtr newPtr{}; - newPtr.MoveFrom(Move(*this)); + newPtr.MoveFromUnsafe(Move(*this)); return newPtr; } return {}; } - template - TPtr Cast() const - { - if constexpr (Derived) // Is T2 is T or its base - { - return TPtr{*this}; - } - - TPtr ptr{*this}; - return ptr.template Cast(); - } - TPtr AsPtr() const { return TPtr{*this}; @@ -283,11 +262,11 @@ namespace p T& operator*() const { - return *GetUnsafe(); + return *Get(); } T* operator->() const { - return GetUnsafe(); + return Get(); } template @@ -298,12 +277,12 @@ namespace p template bool operator==(const TOwnPtr& other) const { - return value == other.GetUnsafe(); + return value == other.Get(); } template bool operator==(const TPtr& other) const { - return value == other.GetUnsafe(); + return value == other.Get(); } template bool operator!=(T2* other) const @@ -313,12 +292,12 @@ namespace p template bool operator!=(const TOwnPtr& other) const { - return value != other.GetUnsafe(); + return value != other.Get(); } template bool operator!=(const TPtr& other) const { - return value != other.GetUnsafe(); + return value != other.Get(); } template @@ -341,12 +320,10 @@ namespace p return Move(ptr); } - private: - template - void MoveFrom(TOwnPtr&& other) + void MoveFromUnsafe(TOwnPtr&& other) { - Super::MoveFrom(Move(other)); + Super::MoveFromUnsafe(Move(other)); #if P_DEBUG instance = static_cast(value); other.instance = nullptr; @@ -374,13 +351,13 @@ namespace p TPtr& operator=(const TPtr& other) { - CopyFrom(other); + CopyFromUnsafe(other); return *this; } TPtr& operator=(TPtr&& other) noexcept { - MoveFrom(Move(other)); + MoveFromUnsafe(Move(other)); return *this; } @@ -405,47 +382,29 @@ namespace p template TPtr& operator=(const TPtr& other) requires Derived { - CopyFrom(other); + CopyFromUnsafe(other); return *this; } template TPtr& operator=(TPtr&& other) noexcept requires Derived { - MoveFrom(Move(other)); + MoveFromUnsafe(Move(other)); return *this; } T* Get() const - { - return IsValid() ? GetUnsafe() : nullptr; - } - - T* GetUnsafe() const { return static_cast(value); } - template - TPtr Cast() const - { - if (IsValid() && (Convertible || dynamic_cast(GetUnsafe()) != nullptr)) - { - TPtr ptr{}; - ptr.CopyFrom(*this); - return ptr; - } - return {}; - } - - T& operator*() const { - return *GetUnsafe(); + return *Get(); } T* operator->() const { - return GetUnsafe(); + return Get(); } template @@ -456,12 +415,12 @@ namespace p template bool operator==(const TOwnPtr& other) const { - return value == other.GetUnsafe(); + return value == other.Get(); } template bool operator==(const TPtr& other) const { - return value == other.GetUnsafe(); + return value == other.Get(); } template bool operator!=(T2* other) const @@ -471,12 +430,12 @@ namespace p template bool operator!=(const TOwnPtr& other) const { - return value != other.GetUnsafe(); + return value != other.Get(); } template bool operator!=(const TPtr& other) const { - return value != other.GetUnsafe(); + return value != other.Get(); } }; @@ -484,7 +443,7 @@ namespace p /////////////////////////////////////////////////// // Non-templated Pointer types - struct PIPE_API OwnPtr : public BaseOwnPtr + struct P_API OwnPtr : public BaseOwnPtr { using Super = BaseOwnPtr; @@ -497,12 +456,12 @@ namespace p template OwnPtr(TOwnPtr&& other) noexcept { - MoveFrom(Move(other)); + MoveFromUnsafe(Move(other)); typeId = GetTypeId(); } OwnPtr(OwnPtr&& other) noexcept { - MoveFrom(Move(other)); + MoveFromUnsafe(Move(other)); typeId = Move(other.typeId); other.typeId = TypeId::None(); } @@ -511,7 +470,7 @@ namespace p OwnPtr& operator=(TOwnPtr&& other) noexcept { Delete(); - MoveFrom(Move(other)); + MoveFromUnsafe(Move(other)); typeId = GetTypeId(); return *this; } @@ -519,7 +478,7 @@ namespace p OwnPtr& operator=(OwnPtr&& other) noexcept { Delete(); - MoveFrom(Move(other)); + MoveFromUnsafe(Move(other)); typeId = Move(other.typeId); other.typeId = TypeId::None(); return *this; @@ -578,7 +537,7 @@ namespace p inline TOwnPtr::TOwnPtr(OwnPtr&& other) noexcept { P_CheckMsg(other.IsType(), "Type doesn't match!"); - Super::MoveFrom(Move(other)); + Super::MoveFromUnsafe(Move(other)); #if P_DEBUG instance = static_cast(value); #endif @@ -589,7 +548,7 @@ namespace p { P_CheckMsg(other.IsType(), "Type doesn't match!"); Delete(); - Super::MoveFrom(Move(other)); + Super::MoveFromUnsafe(Move(other)); #if P_DEBUG instance = static_cast(value); #endif diff --git a/Include/Pipe/Memory/PtrBuilder.h b/Include/Pipe/Memory/PtrBuilder.h index 7c107f5e..17f8e81c 100644 --- a/Include/Pipe/Memory/PtrBuilder.h +++ b/Include/Pipe/Memory/PtrBuilder.h @@ -3,7 +3,7 @@ #pragma once #include "Pipe/Core/TypeTraits.h" -#include "Pipe/Memory/Alloc.h" +#include "PipeMemory.h" namespace p diff --git a/Include/Pipe/Memory/STLAllocator.h b/Include/Pipe/Memory/STLAllocator.h deleted file mode 100644 index 925023aa..00000000 --- a/Include/Pipe/Memory/STLAllocator.h +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved -#pragma once - -#include "Pipe/Core/Limits.h" -#include "Pipe/Core/Platform.h" -#include "Pipe/Core/Utility.h" -#include "Pipe/Memory/Alloc.h" -#include "Pipe/Memory/Arena.h" - -#include - - -namespace p -{ - template - struct STLAllocator - { - using value_type = T; - using size_type = sizet; - using difference_type = std::ptrdiff_t; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = value_type*; - using const_pointer = const value_type*; - - template - struct rebind - { - using other = STLAllocator; - }; - - Arena* arena = nullptr; - - - STLAllocator(Arena& arena = GetCurrentArena()) noexcept : arena{&arena} {} - STLAllocator(const STLAllocator& other) noexcept : arena{other.arena} {} - template - STLAllocator(const STLAllocator& other) noexcept : arena{other.arena} - {} - STLAllocator select_on_container_copy_construction() const - { - return *this; - } - -#if (__cplusplus >= 201703L) // C++17 - P_NODISCARD T* allocate(size_type count) - { - return p::Alloc(*arena, count); - } - P_NODISCARD T* allocate(size_type count, const void*) - { - return allocate(count); - } -#else - P_NODISCARD pointer allocate(size_type count, const void* = 0) - { - return p::Alloc(*arena, count); - } -#endif - constexpr void deallocate(T* p, size_type n) - { - p::Free(*arena, p, n); - } - - -#if ((__cplusplus >= 201103L) || (_MSC_VER > 1900)) // C++11 - using is_always_equal = std::true_type; - using propagate_on_container_copy_assignment = std::true_type; - using propagate_on_container_move_assignment = std::true_type; - using propagate_on_container_swap = std::true_type; - template - void construct(U* p, Args&&... args) - { - ::new (p) U(p::Forward(args)...); - } - template - void destroy(U* p) noexcept - { - p->~U(); - } -#else - void construct(pointer p, value_type const& val) - { - ::new (p) value_type(val); - } - void destroy(pointer p) - { - p->~value_type(); - } -#endif - - size_type max_size() const noexcept - { - return (Limits::Max() / sizeof(value_type)); - } - pointer address(reference x) const - { - return &x; - } - const_pointer address(const_reference x) const - { - return &x; - } - }; - - template - bool operator==(const STLAllocator& a, const STLAllocator& b) noexcept - { - return &a.arena == &b.arena; - } - template - bool operator!=(const STLAllocator& a, const STLAllocator& b) noexcept - { - return &a.arena != &b.arena; - } -} // namespace p diff --git a/Include/PipeAlgorithms.h b/Include/PipeAlgorithms.h index bea7b04a..abcf3b09 100644 --- a/Include/PipeAlgorithms.h +++ b/Include/PipeAlgorithms.h @@ -486,9 +486,13 @@ namespace p for (;;) { while (++inner.min <= current.max && !predicate(*current.min, *inner.min)) + { ; + } while (--inner.max > current.min && !predicate(*inner.max, *current.min)) + { ; + } if (inner.min > inner.max) { break; @@ -557,5 +561,5 @@ namespace p #pragma endregion Transformations /** Generates CRC hash of the memory area */ - PIPE_API u32 MemCrc32(const void* Data, i32 Length, u32 CRC = 0); + P_API u32 MemCrc32(const void* Data, i32 Length, u32 CRC = 0); } // namespace p diff --git a/Include/PipeArrays.h b/Include/PipeArrays.h index bf5a4942..e3faf9c9 100644 --- a/Include/PipeArrays.h +++ b/Include/PipeArrays.h @@ -4,14 +4,13 @@ #include "Pipe/Core/Checks.h" #include "Pipe/Core/Function.h" -#include "Pipe/Core/Platform.h" +#include "Pipe/Core/Hash.h" #include "Pipe/Core/Utility.h" -#include "Pipe/Memory/Alloc.h" -#include "Pipe/Memory/Arena.h" -#include "Pipe/Memory/Memory.h" #include "PipeAlgorithms.h" #include "PipeArraysFwd.h" #include "PipeMath.h" +#include "PipeMemory.h" +#include "PipePlatform.h" #include #include @@ -99,7 +98,7 @@ namespace p constexpr TArrayIterator& operator++() noexcept { - Verify(); + VerifyOffset(1); ++ptr; return *this; } @@ -113,7 +112,7 @@ namespace p constexpr TArrayIterator& operator--() noexcept { - Verify(); + VerifyOffset(-1); --ptr; return *this; } @@ -428,6 +427,29 @@ namespace p } } + constexpr i32 Rotate(i32 num) + { + if (IsEmpty()) + { + return 0; + } + num %= Size(); + if (num == 0) + { + return num; + } + else if (num < 0) + { + num += Size(); + } + + auto mid = begin() + num; + std::reverse(begin(), mid); + std::reverse(mid, end()); + std::reverse(begin(), end()); + return num; + } + template void Sort(Predicate predicate) { @@ -618,6 +640,42 @@ namespace p { return FindSorted(value, sortPredicate) != NO_INDEX; } + + constexpr const Type* Max() + { + const Type* latestMax = nullptr; + if (size > 0) + { + latestMax = &data[0]; + for (i32 i = 1; i < size; ++i) + { + const Type& value = data[i]; + if (value > *latestMax) + { + latestMax = &value; + } + } + } + return latestMax; + } + + constexpr const Type* Min() + { + const Type* latestMin = nullptr; + if (size > 0) + { + latestMin = &data[0]; + for (i32 i = 1; i < size; ++i) + { + const Type& value = data[i]; + if (value < *latestMin) + { + latestMin = &value; + } + } + } + return latestMin; + } #pragma endregion Search template @@ -909,26 +967,32 @@ namespace p if (!sortPredicate(value, Super::data[index])) // Equal check, found element { if (outAdded) + { *outAdded = false; + } return index; } else if (insertSorted) { Insert(index, value); if (outAdded) + { *outAdded = true; + } return index; } } if (outAdded) + { *outAdded = true; + } return Add(value); } template> i32 AddUniqueSorted(Type&& value, SortPredicate sortPredicate = {}, - bool* outAdded = nullptr, bool insertSorted = true) + bool* outAdded = nullptr, bool insertSorted = true) requires(Hashable) { const i32 index = Super::LowerBound(value, sortPredicate); if (index != NO_INDEX) @@ -936,20 +1000,26 @@ namespace p if (!sortPredicate(value, Super::data[index])) // Equal check, found element { if (outAdded) + { *outAdded = false; + } return index; } else if (insertSorted) { Insert(index, p::Forward(value)); if (outAdded) + { *outAdded = true; + } return index; } } if (outAdded) + { *outAdded = true; + } return Add(p::Forward(value)); } @@ -1207,7 +1277,9 @@ namespace p /// @OPTIMIZE: Shrinking can be combined to avoid moving trailing elements twice if (shouldShrink) + { Shrink(); + } } /** @@ -1225,7 +1297,9 @@ namespace p /// @OPTIMIZE: Shrinking can be combined to avoid moving trailing elements twice if (shouldShrink) + { Shrink(); + } } /** diff --git a/Include/PipeArraysFwd.h b/Include/PipeArraysFwd.h index 348ee26e..55bf5748 100644 --- a/Include/PipeArraysFwd.h +++ b/Include/PipeArraysFwd.h @@ -2,7 +2,7 @@ #pragma once -#include "Pipe/Core/Platform.h" +#include "PipePlatform.h" namespace p diff --git a/Include/PipeColor.h b/Include/PipeColor.h index f3a0044f..7f084253 100644 --- a/Include/PipeColor.h +++ b/Include/PipeColor.h @@ -298,7 +298,7 @@ namespace p // Gets the color in a packed u32 format packed in the order RGBA. constexpr u32 ToPackedRGBA() const requires(mode == ColorMode::RGBA) { - return (this->r << 24) | (this->g << 16) | (this->g << 8) | (this->a << 0); + return (this->r << 24) | (this->g << 16) | (this->b << 8) | (this->a << 0); } // Gets the color in a packed u32 format packed in the order BGRA. @@ -462,16 +462,24 @@ namespace p constexpr auto* Data() { if constexpr (mode == ColorMode::HSV) + { return &this->h; + } else + { return &this->r; + } } constexpr const auto* Data() const { if constexpr (mode == ColorMode::HSV) + { return &this->h; + } else + { return &this->r; + } } constexpr auto& operator[](u32 i) { @@ -652,10 +660,9 @@ namespace p { if constexpr (mode == ColorMode::HSV) { - return Strings::Format( - TX("(h={},s={},v={},a={})"), this->h, this->s, this->v, this->a); + return Strings::Format("(h={},s={},v={},a={})", this->h, this->s, this->v, this->a); } - return Strings::Format(TX("(r={},g={},b={},a={})"), this->r, this->g, this->b, this->a); + return Strings::Format("(r={},g={},b={},a={})", this->r, this->g, this->b, this->a); } /** @@ -669,9 +676,9 @@ namespace p if (includeAlpha) { return Strings::Format( - TX("{:02X}{:02X}{:02X}{:02X}"), this->r, this->g, this->b, this->a); + "{:02X}{:02X}{:02X}{:02X}", this->r, this->g, this->b, this->a); } - return Strings::Format(TX("{:02X}{:02X}{:02X}"), this->r, this->g, this->b); + return Strings::Format("{:02X}{:02X}{:02X}", this->r, this->g, this->b); } // Common colors diff --git a/Include/PipeECS.h b/Include/PipeECS.h index cf51fbfb..39915f55 100644 --- a/Include/PipeECS.h +++ b/Include/PipeECS.h @@ -4,11 +4,11 @@ #include "Pipe/Core/Broadcast.h" #include "Pipe/Core/Map.h" #include "Pipe/Core/PageBuffer.h" -#include "Pipe/Core/Platform.h" #include "Pipe/Core/Templates.h" #include "Pipe/Core/TypeTraits.h" #include "Pipe/Memory/UniquePtr.h" #include "PipeArrays.h" +#include "PipePlatform.h" #include "PipeReflect.h" @@ -111,11 +111,11 @@ namespace p * If a context is provided, providing the index alone as a number will resolve it slast valid * version */ - PIPE_API Id IdFromString(String str, EntityContext* context); + P_API Id IdFromString(String str, EntityContext* context); /** IdRegistry tracks the existance and versioning of ids. Used internally by the ECS context */ - struct PIPE_API IdRegistry + struct P_API IdRegistry { using Traits = IdTraits; using Index = Traits::Index; @@ -162,8 +162,11 @@ namespace p struct CNotSerialized {}; - class PIPE_API EntityReader : public Reader + class P_API EntityReader : public Reader { + using Super = Reader; + P_STRUCT(EntityReader) + EntityContext& context; // While serializing we create ids as Ids appear and link them. @@ -191,11 +194,20 @@ namespace p const TArray& GetIds() const; EntityContext& GetContext(); + + protected: + TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } }; - class PIPE_API EntityWriter : public Writer + class P_API EntityWriter : public Writer { + using Super = Writer; + P_STRUCT(EntityWriter) + EntityContext& context; // While serializing we create ids as Ids appear and link them. @@ -231,10 +243,16 @@ namespace p void RetrieveHierarchy(const TArray& roots, TArray& children); void RemoveIgnoredEntities(TArray& entities); void MapIdsToIndices(); + + protected: + TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } }; - void PIPE_API Read(p::Reader& ct, p::Id& val); - void PIPE_API Write(p::Writer& ct, p::Id val); + void P_API Read(p::Reader& ct, p::Id& val); + void P_API Write(p::Writer& ct, p::Id val); //////////////////////////////// @@ -248,7 +266,7 @@ namespace p }; /** Iterates the Ids contained in a pool */ - struct PIPE_API PoolIterator final + struct P_API PoolIterator final { using difference_type = typename IdTraits::Difference; using value_type = Id; @@ -350,7 +368,7 @@ namespace p }; - struct PIPE_API BasePool + struct P_API BasePool { using Index = IdTraits::Index; @@ -715,9 +733,13 @@ namespace p P_Check(Has(id)); OnRemoved({id}); if (deletionPolicy == DeletionPolicy::InPlace) + { Pop(id); + } else + { PopSwap(id); + } } i32 Remove(TView ids) override @@ -905,7 +927,7 @@ namespace p }; - struct PIPE_API PoolInstance + struct P_API PoolInstance { TypeId componentId{}; TUniquePtr pool; @@ -925,1339 +947,1330 @@ namespace p // CONTEXT // - struct PIPE_API SortLessStatics - { - bool operator()(const OwnPtr& a, const OwnPtr& b) const - { - return a.GetId() < b.GetId(); - } + struct P_API SortLessStatics{ + bool operator()(const OwnPtr& a, const OwnPtr& b) const {return a.GetId() < b.GetId(); +} // namespace p - bool operator()(TypeId a, const OwnPtr& b) const - { - return a < b.GetId(); - } +bool operator()(TypeId a, const OwnPtr& b) const +{ + return a < b.GetId(); +} - bool operator()(const OwnPtr& a, TypeId b) const - { - return a.GetId() < b; - } - }; +bool operator()(const OwnPtr& a, TypeId b) const +{ + return a.GetId() < b; +} +} +; - struct PIPE_API EntityContext - { - private: - IdRegistry idRegistry; - mutable TArray pools; - TArray statics; +struct P_API EntityContext +{ +private: + IdRegistry idRegistry; + mutable TArray pools; + TArray statics; - public: - EntityContext(); - ~EntityContext() - { - Reset(); - } - explicit EntityContext(const EntityContext& other) noexcept; - explicit EntityContext(EntityContext&& other) noexcept; - EntityContext& operator=(const EntityContext& other) noexcept; - EntityContext& operator=(EntityContext&& other) noexcept; +public: + EntityContext(); + ~EntityContext() + { + Reset(); + } + explicit EntityContext(const EntityContext& other) noexcept; + explicit EntityContext(EntityContext&& other) noexcept; + EntityContext& operator=(const EntityContext& other) noexcept; + EntityContext& operator=(EntityContext&& other) noexcept; #pragma region Entities - Id Create(); - void Create(Id id); - void Create(TView ids); - void Destroy(Id id); - void Destroy(TView ids); - - // Reflection helpers - void* AddDefault(TypeId typeId, Id id); - void Remove(TypeId typeId, Id id); - - // Adds Component to an entity (if the entity doesnt have it already) - template - decltype(auto) Add(Id id, C&& value = {}) const requires(IsSame>) - { - P_Check(IsValid(id)); - return AssurePool().Add(id, p::Forward(value)); - } - template - decltype(auto) Add(Id id, const C& value) const requires(IsSame>) - { - P_Check(IsValid(id)); - return AssurePool().Add(id, value); - } - // Adds Component to an entity (if the entity doesnt have it already) - template - void Add(Id id) requires(sizeof...(Component) > 1) - { - P_Check(IsValid(id)); - (Add(id), ...); - } - - // Add Component to many entities (if they dont have it already) - template - decltype(auto) AddN(TView ids, const Component& value = {}) - { - return AssurePool().Add(ids.begin(), ids.end(), value); - } + Id Create(); + void Create(Id id); + void Create(TView ids); + void Destroy(Id id); + void Destroy(TView ids); + + // Reflection helpers + void* AddDefault(TypeId typeId, Id id); + void Remove(TypeId typeId, Id id); + + // Adds Component to an entity (if the entity doesnt have it already) + template + decltype(auto) Add(Id id, C&& value = {}) const requires(IsSame>) + { + P_Check(IsValid(id)); + return AssurePool().Add(id, p::Forward(value)); + } + template + decltype(auto) Add(Id id, const C& value) const requires(IsSame>) + { + P_Check(IsValid(id)); + return AssurePool().Add(id, value); + } + // Adds Component to an entity (if the entity doesnt have it already) + template + void Add(Id id) requires(sizeof...(Component) > 1) + { + P_Check(IsValid(id)); + (Add(id), ...); + } - // Add Components to many entities (if they don't have it already) - template - void AddN(TView ids) requires(sizeof...(Component) > 1) - { - (Add(ids), ...); - } + // Add Component to many entities (if they dont have it already) + template + decltype(auto) AddN(TView ids, const Component& value = {}) + { + return AssurePool().Add(ids.begin(), ids.end(), value); + } - template - void AddN(TView ids, const TView& values) - { - P_Check(ids.Size() == values.Size()); - AssurePool().Add(ids.begin(), ids.end(), values.begin()); - } + // Add Components to many entities (if they don't have it already) + template + void AddN(TView ids) requires(sizeof...(Component) > 1) + { + (Add(ids), ...); + } + template + void AddN(TView ids, const TView& values) + { + P_Check(ids.Size() == values.Size()); + AssurePool().Add(ids.begin(), ids.end(), values.begin()); + } - template - void Remove(const Id id) - { - if (auto* pool = GetPool()) - { - pool->Remove(id); - } - } - template - void Remove(const Id id) requires(sizeof...(Component) > 1) - { - (Remove(id), ...); - } - template - void Remove(TView ids) - { - if (auto* pool = GetPool()) - { - pool->Remove(ids); - } - } - template - void Remove(TView ids) requires(sizeof...(Component) > 1) - { - (Remove(ids), ...); - } - template - Component& Get(const Id id) const - { - P_Check(IsValid(id)); - auto* const pool = GetPool(); - P_Check(pool); - return pool->Get(id); - } - template - TTuple Get(const Id id) const requires(sizeof...(Component) > 1) - { - P_Check(IsValid(id)); - return std::forward_as_tuple(Get(id)...); - } - template - Component* TryGet(const Id id) const + template + void Remove(const Id id) + { + if (auto* pool = GetPool()) { - auto* const pool = GetPool(); - return pool ? pool->TryGet(id) : nullptr; + pool->Remove(id); } - template - TTuple TryGet(const Id id) const requires(sizeof...(Component) > 1) + } + template + void Remove(const Id id) requires(sizeof...(Component) > 1) + { + (Remove(id), ...); + } + template + void Remove(TView ids) + { + if (auto* pool = GetPool()) { - P_Check(IsValid(id)); - return std::forward_as_tuple(TryGet(id)...); + pool->Remove(ids); } + } + template + void Remove(TView ids) requires(sizeof...(Component) > 1) + { + (Remove(ids), ...); + } - template - Component& GetOrAdd(Id id) - { - P_Check(IsValid(id)); - return AssurePool().GetOrAdd(id); - } + template + Component& Get(const Id id) const + { + P_Check(IsValid(id)); + auto* const pool = GetPool(); + P_Check(pool); + return pool->Get(id); + } + template + TTuple Get(const Id id) const requires(sizeof...(Component) > 1) + { + P_Check(IsValid(id)); + return std::forward_as_tuple(Get(id)...); + } + template + Component* TryGet(const Id id) const + { + auto* const pool = GetPool(); + return pool ? pool->TryGet(id) : nullptr; + } + template + TTuple TryGet(const Id id) const requires(sizeof...(Component) > 1) + { + P_Check(IsValid(id)); + return std::forward_as_tuple(TryGet(id)...); + } - template - bool Has(Id id) const - { - const auto* pool = GetPool(); - return pool && pool->Has(id); - } + template + Component& GetOrAdd(Id id) + { + P_Check(IsValid(id)); + return AssurePool().GetOrAdd(id); + } - const IdRegistry& GetIdRegistry() const - { - return idRegistry; - } - bool IsValid(Id id) const; - bool WasRemoved(Id id) const; - bool IsOrphan(const Id id) const; + template + bool Has(Id id) const + { + const auto* pool = GetPool(); + return pool && pool->Has(id); + } - template - void Each(Callback cb) const - { - idRegistry.Each(cb); - } + const IdRegistry& GetIdRegistry() const + { + return idRegistry; + } + bool IsValid(Id id) const; + bool WasRemoved(Id id) const; + bool IsOrphan(const Id id) const; - u32 Size() const - { - return idRegistry.Size(); - } + template + void Each(Callback cb) const + { + idRegistry.Each(cb); + } - template - void EachOrphan(Callback cb) const - { - Each([this, &cb](const Id id) { - if (IsOrphan(id)) - { - cb(id); - } - }); - } + u32 Size() const + { + return idRegistry.Size(); + } - template - void ClearPool() - { - if (auto* pool = GetPool()) + template + void EachOrphan(Callback cb) const + { + Each([this, &cb](const Id id) { + if (IsOrphan(id)) { - pool->Clear(); + cb(id); } - } + }); + } - template - void ClearPool() requires(sizeof...(Component) > 1) + template + void ClearPool() + { + if (auto* pool = GetPool()) { - (ClearPool(), ...); + pool->Clear(); } + } - void Reset(bool keepStatics = false); + template + void ClearPool() requires(sizeof...(Component) > 1) + { + (ClearPool(), ...); + } - template - TBroadcast>& OnAdd() - { - return AssurePool().OnAdd(); - } + void Reset(bool keepStatics = false); - template - TBroadcast>& OnRemove() - { - return AssurePool().OnRemove(); - } + template + TBroadcast>& OnAdd() + { + return AssurePool().OnAdd(); + } - // Finds or creates a pool - template - TPool>& AssurePool() const; + template + TBroadcast>& OnRemove() + { + return AssurePool().OnRemove(); + } - BasePool* GetPool(TypeId componentId) const; - void GetPools(TView componentIds, TArray& outPools) const; - void GetPools(TView componentIds, TArray& outPools) const - { - GetPools(componentIds, reinterpret_cast&>(outPools)); - } + // Finds or creates a pool + template + TPool>& AssurePool() const; - template - CopyConst>, T>* GetPool() const - { - return static_cast>, T>*>(GetPool(GetTypeId>())); - } + BasePool* GetPool(TypeId componentId) const; + void GetPools(TView componentIds, TArray& outPools) const; + void GetPools(TView componentIds, TArray& outPools) const + { + GetPools(componentIds, reinterpret_cast&>(outPools)); + } - const TArray& GetPools() const - { - return pools; - } + template + CopyConst>, T>* GetPool() const + { + return static_cast>, T>*>(GetPool(GetTypeId>())); + } + + const TArray& GetPools() const + { + return pools; + } #pragma endregion Entities #pragma region Statics - void* TryGetStatic(TypeId typeId); - const void* TryGetStatic(TypeId typeId) const; - bool HasStatic(TypeId typeId) const; - bool RemoveStatic(TypeId typeId); + void* TryGetStatic(TypeId typeId); + const void* TryGetStatic(TypeId typeId) const; + bool HasStatic(TypeId typeId) const; + bool RemoveStatic(TypeId typeId); - template - Static& SetStatic(); - template - Static& SetStatic(Static&& value); - template - Static& SetStatic(const Static& value); - template - Static& GetOrSetStatic(); - template - Static& GetOrSetStatic(Static&& newValue); - template - Static& GetOrSetStatic(const Static& newValue); - template - Static& GetStatic() - { - return *TryGetStatic(); - } - template - const Static& GetStatic() const - { - return *TryGetStatic(); - } - template - Static* TryGetStatic() - { - return static_cast(TryGetStatic(GetTypeId>())); - } - template - const Static* TryGetStatic() const - { - return static_cast(TryGetStatic(GetTypeId>())); - } - template - bool HasStatic() const - { - return HasStatic(GetTypeId>()); - } - template - bool RemoveStatic() - { - return RemoveStatic(GetTypeId>()); - } + template + Static& SetStatic(); + template + Static& SetStatic(Static&& value); + template + Static& SetStatic(const Static& value); + template + Static& GetOrSetStatic(); + template + Static& GetOrSetStatic(Static&& newValue); + template + Static& GetOrSetStatic(const Static& newValue); + template + Static& GetStatic() + { + return *TryGetStatic(); + } + template + const Static& GetStatic() const + { + return *TryGetStatic(); + } + template + Static* TryGetStatic() + { + return static_cast(TryGetStatic(GetTypeId>())); + } + template + const Static* TryGetStatic() const + { + return static_cast(TryGetStatic(GetTypeId>())); + } + template + bool HasStatic() const + { + return HasStatic(GetTypeId>()); + } + template + bool RemoveStatic() + { + return RemoveStatic(GetTypeId>()); + } #pragma endregion Statics - private: - void CopyFrom(const EntityContext& other); - void MoveFrom(EntityContext&& other); +private: + void CopyFrom(const EntityContext& other); + void MoveFrom(EntityContext&& other); - OwnPtr& FindOrAddStaticPtr( - TArray& statics, const TypeId typeId, bool* bAdded = nullptr); + static OwnPtr& FindOrAddStaticPtr( + TArray& statics, const TypeId typeId, bool* bAdded = nullptr); - template - PoolInstance CreatePoolInstance() const; - }; + template + PoolInstance CreatePoolInstance() const; +}; - //////////////////////////////// - // ACCESSES - // +//////////////////////////////// +// ACCESSES +// - enum class AccessMode : u8 - { - Read, - Write - }; +enum class AccessMode : u8 +{ + Read, + Write +}; - struct TypeAccess - { - TypeId typeId = TypeId::None(); - AccessMode mode = AccessMode::Read; +struct TypeAccess +{ + TypeId typeId = TypeId::None(); + AccessMode mode = AccessMode::Read; - constexpr TypeAccess() = default; - constexpr TypeAccess(TypeId typeId, AccessMode mode) : typeId{typeId}, mode{mode} {} - }; + constexpr TypeAccess() = default; + constexpr TypeAccess(TypeId typeId, AccessMode mode) : typeId{typeId}, mode{mode} {} +}; - template - struct TTypeAccess : TypeAccess - { - using Type = Mut; +template +struct TTypeAccess : TypeAccess +{ + using Type = Mut; - constexpr TTypeAccess() : TypeAccess(GetTypeId(), inMode) {} - }; + constexpr TTypeAccess() : TypeAccess(GetTypeId(), inMode) {} +}; - template - struct TRead : public TTypeAccess - {}; +template +struct TRead : public TTypeAccess +{}; - template - struct TWrite : public TTypeAccess - {}; +template +struct TWrite : public TTypeAccess +{}; - template - struct TTypeAccessInfo - { - using Type = Mut; - static constexpr AccessMode mode = AccessMode::Read; - }; - template - requires Derived - struct TTypeAccessInfo - { - using Type = typename T::Type; - static constexpr AccessMode mode = T().mode; - }; - template - using AsComponent = typename TTypeAccessInfo::Type; +template +struct TTypeAccessInfo +{ + using Type = Mut; + static constexpr AccessMode mode = AccessMode::Read; +}; +template +requires Derived +struct TTypeAccessInfo +{ + using Type = typename T::Type; + static constexpr AccessMode mode = T().mode; +}; +template +using AsComponent = typename TTypeAccessInfo::Type; - template - struct TAccess - { - template - friend struct TAccess; +template +struct TAccess +{ + template + friend struct TAccess; - using Components = TTypeList; - using RawComponents = TTypeList...>; + using Components = TTypeList; + using RawComponents = TTypeList...>; - private: - TypeId typeId; - EntityContext& context; - TTuple>*...> pools; +private: + TypeId typeId; + EntityContext& context; + TTuple>*...> pools; - public: - TAccess(EntityContext& context) - : context{context}, pools{&context.AssurePool>()...} - {} - TAccess(const TAccess& other) : context{other.context}, pools{other.pools} {} +public: + TAccess(EntityContext& context) + : context{context}, pools{&context.AssurePool>()...} + {} + TAccess(const TAccess& other) : context{other.context}, pools{other.pools} {} - // Construct a child access (super-set) from another access - template - TAccess(const TAccess& other) : context{other.context} - { - using Other = TAccess; + // Construct a child access (super-set) from another access + template + TAccess(const TAccess& other) : context{other.context} + { + using Other = TAccess; - constexpr bool validConstants = (Other::template HasType() && ...); - constexpr bool validMutables = - ((Other::template IsWritable() || TTypeAccessInfo::mode != AccessMode::Write) - && ...); - static_assert(validConstants, "Parent access lacks dependencies from this access."); - static_assert( - validMutables, "Parent access lacks *mutable* dependencies from this access."); + constexpr bool validConstants = (Other::template HasType() && ...); + constexpr bool validMutables = + ((Other::template IsWritable() || TTypeAccessInfo::mode != AccessMode::Write) + && ...); + static_assert(validConstants, "Parent access lacks dependencies from this access."); + static_assert( + validMutables, "Parent access lacks *mutable* dependencies from this access."); - if constexpr (validConstants && validMutables) - { - pools = {std::get>*>(other.pools)...}; - } + if constexpr (validConstants && validMutables) + { + pools = {std::get>*>(other.pools)...}; } + } - template - TPool>* GetPool() const requires(IsMutable) + template + TPool>* GetPool() const requires(IsMutable) + { + static_assert(IsWritable(), "Can't modify components of this type"); + if constexpr (IsWritable()) // Prevent missleading errors if condition fails { - static_assert(IsWritable(), "Can't modify components of this type"); - if constexpr (IsWritable()) // Prevent missleading errors if condition fails - { - return std::get>*>(pools); - } - else - { - return nullptr; - } + return std::get>*>(pools); } - - template - const TPool>* GetPool() const requires(IsConst) - { - static_assert(IsReadable(), "Can't read components of this type"); - if constexpr (IsReadable()) // Prevent missleading errors if condition fails - { - return std::get>*>(pools); - } - else - { - return nullptr; - } - } - - template - TPool>& AssurePool() const requires(IsMutable) - { - return *GetPool(); - } - - template - const TPool>& AssurePool() const requires(IsConst) - { - return *GetPool(); - } - - bool IsValid(Id id) const - { - return context.IsValid(id); - } - - template - bool Has(Id id) const requires(sizeof...(C) >= 1) - { - return (GetPool()->Has(id) && ...); - } - - template - decltype(auto) Add(Id id, C&& value = {}) const requires(IsSame>) - { - return GetPool()->Add(id, p::Forward(value)); - } - template - decltype(auto) Add(Id id, const C& value) const requires(IsSame>) - { - return GetPool()->Add(id, value); - } - - // Add component to an entities (if they dont have it already) - template - void Add(Id id) const requires((IsSame> && ...) && sizeof...(C) > 1) - { - (Add(id), ...); - } - - // Add component to many entities (if they dont have it already) - template - decltype(auto) AddN(TView ids, const C& value = {}) const - { - return GetPool()->Add(ids.begin(), ids.end(), value); - } - - // Add components to many entities (if they dont have it already) - template - void AddN(TView ids) const - requires((IsSame> && ...) && sizeof...(C) > 1) - { - (AddN(ids), ...); - } - - - template - void Remove(const Id id) const requires(IsSame>) - { - if (auto* pool = GetPool()) - { - pool->Remove(id); - } - } - template - void Remove(const Id id) const requires(sizeof...(C) > 1) - { - (Remove(id), ...); - } - template - void Remove(TView ids) const requires(IsSame>) - { - if (auto* pool = GetPool()) - { - pool->Remove(ids); - } - } - template - void Remove(TView ids) const requires(sizeof...(C) > 1) - { - (Remove(ids), ...); - } - - template - C& Get(Id id) const - { - return GetPool()->Get(id); - } - - template - C* TryGet(Id id) const - { - return GetPool()->TryGet(id); - } - - template - C& GetOrAdd(Id id) const requires(IsMutable) - { - return GetPool()->GetOrAdd(id); - } - - i32 Size() const - { - static_assert(sizeof...(T) == 1, "Can only get the size of single component accesses"); - return GetPool()->Size(); - } - - template - i32 Size() const - { - return GetPool()->Size(); - } - - EntityContext& GetContext() const - { - return context; - } - - - template - static constexpr bool HasType() - { - return ListContains>(); - } - - template - static constexpr bool IsReadable() - { - return HasType(); - } - - template - static constexpr bool IsWritable() - { - return IsMutable && ListContains>>(); - } - }; - - template - using TAccessRef = const TAccess&; - - struct Access - { - protected: - - EntityContext& ast; - TArray types; - TArray pools; - - - public: - Access(EntityContext& ast, const TArray& types) : ast{ast} {} - - template - Access(TAccessRef access) : ast{access.ast} - {} - - template - TPool>* GetPool() const requires(IsMutable) - { - return nullptr; - } - - template - const TPool>* GetPool() const requires(IsConst) + else { return nullptr; } - - private: - - i32 GetPoolIndex() const - { - return 0; - } - }; - - - //////////////////////////////// - // FILTERING - // - - /** Remove ids containing a component from 'ids'. Does not guarantee order. */ - PIPE_API void ExcludeIdsWith( - const BasePool* pool, TArray& ids, const bool shouldShrink = true); - - /** Remove ids containing a component from 'ids'. Guarantees order. */ - PIPE_API void ExcludeIdsWithStable( - const BasePool* pool, TArray& ids, const bool shouldShrink = true); - - /** Remove ids NOT containing a component from 'ids'. Does not guarantee order. */ - PIPE_API void ExcludeIdsWithout( - const BasePool* pool, TArray& ids, const bool shouldShrink = true); - - /** Remove ids NOT containing a component from 'ids'. Guarantees order. */ - PIPE_API void ExcludeIdsWithoutStable( - const BasePool* pool, TArray& ids, const bool shouldShrink = true); - - - /** Find ids containing a component from a list 'source' into 'results'. */ - PIPE_API void FindIdsWith(const BasePool* pool, TView source, TArray& results); - PIPE_API void FindIdsWith( - TView pools, TView source, TArray& results); - - /** Find ids NOT containing a component from a list 'source' into 'results'. */ - PIPE_API void FindIdsWithout(const BasePool* pool, TView source, TArray& results); - - - /** - * Find and remove ids containing a component from list 'source' into 'results'. - * Does not guarantee order. - */ - PIPE_API void ExtractIdsWith(const BasePool* pool, TArray& source, TArray& results, - const bool shouldShrink = true); - - /** - * Find and remove ids containing a component from list 'source' into 'results'. - * Guarantees order. - */ - PIPE_API void ExtractIdsWithStable(const BasePool* pool, TArray& source, - TArray& results, const bool shouldShrink = true); - - /** - * Find and remove ids containing a component from list 'source' into 'results'. - * Does not guarantee order. - */ - PIPE_API void ExtractIdsWithout(const BasePool* pool, TArray& source, TArray& results, - const bool shouldShrink = true); - - /** - * Find and remove ids not containing a component from list 'source' into 'results'. - * Guarantees order. - */ - PIPE_API void ExtractIdsWithoutStable(const BasePool* pool, TArray& source, - TArray& results, const bool shouldShrink = true); - - - /** Find all ids containing all of the components */ - PIPE_API void FindAllIdsWith(TView pools, TArray& ids); - - /** Find all ids containing any of the components. Includes possible duplicates */ - PIPE_API void FindAllIdsWithAny(TView pools, TArray& ids); - - /** Find all ids containing any of the components. Prevents duplicates */ - PIPE_API void FindAllIdsWithAnyUnique(TView pools, TArray& ids); - - - // Templated API - - /** - * Remove ids containing a component from 'ids'. Does not guarantee order. - * - * @param access from where to access pools - * @param ids array that will be modified - * @param shouldShrink if true, the ids array will be shrink at the end - * @see ExcludeWithStable(), ExcludeWithout() - */ - template - void ExcludeIdsWith(const AccessType& access, TArray& ids, const bool shouldShrink = true) - { - ExcludeIdsWith(&access.template AssurePool(), ids, shouldShrink); - } - template - void ExcludeIdsWith(const AccessType& access, TArray& ids, const bool shouldShrink = true) - requires(sizeof...(C) > 1) - { - (ExcludeIdsWith(access, ids, shouldShrink), ...); } - template - void ExcludeIdsWith(TArray& ids, Predicate predicate, const bool shouldShrink = true) + template + const TPool>* GetPool() const requires(IsConst) { - for (i32 i = ids.Size() - 1; i >= 0; --i) + static_assert(IsReadable(), "Can't read components of this type"); + if constexpr (IsReadable()) // Prevent missleading errors if condition fails { - if (predicate(ids[i])) - { - ids.RemoveAtSwapUnsafe(i); - } + return std::get>*>(pools); } - if (shouldShrink) + else { - ids.Shrink(); + return nullptr; } } - /** - * Remove ids containing a component from 'ids'. Guarantees order. - * - * @param access from where to access pools - * @param ids array that will be modified - * @param shouldShrink if true, the ids array will be shrink at the end - * @see ExcludeWith(), ExcludeWithoutStable() - */ - template - void ExcludeIdsWithStable( - const AccessType& access, TArray& ids, const bool shouldShrink = true) + template + TPool>& AssurePool() const requires(IsMutable) { - ExcludeIdsWithStable(&access.template AssurePool(), ids, shouldShrink); + return *GetPool(); } - template - void ExcludeIdsWithStable(const AccessType& access, TArray& ids, - const bool shouldShrink = true) requires(sizeof...(C) > 1) + + template + const TPool>& AssurePool() const requires(IsConst) { - (ExcludeIdsWithStable(access, ids, shouldShrink), ...); + return *GetPool(); } - /** - * Remove ids NOT containing a component from 'ids'. Does not guarantee order. - * - * @param access from where to access pools - * @param ids array that will be modified - * @param shouldShrink if true, the ids array will be shrink at the end - * @see ExcludeWithoutStable(), ExcludeWith() - */ - template - void ExcludeIdsWithout( - const AccessType& access, TArray& ids, const bool shouldShrink = true) + bool IsValid(Id id) const { - ExcludeIdsWithout(&access.template AssurePool(), ids, shouldShrink); + return context.IsValid(id); } - template - void ExcludeIdsWithout(const AccessType& access, TArray& ids, - const bool shouldShrink = true) requires(sizeof...(C) > 1) + template + bool Has(Id id) const requires(sizeof...(C) >= 1) { - (ExcludeIdsWithout(access, ids, shouldShrink), ...); + return (GetPool()->Has(id) && ...); } - /** - * Remove ids NOT containing a component from 'ids'. Guarantees order. - * - * @param access from where to access pools - * @param ids array that will be modified - * @param shouldShrink if true, the ids array will be shrink at the end - * @see ExcludeWithout(), ExcludeWithStable() - */ - template - void ExcludeIdsWithoutStable( - const AccessType& access, TArray& ids, const bool shouldShrink = true) + template + decltype(auto) Add(Id id, C&& value = {}) const requires(IsSame>) { - ExcludeIdsWithoutStable(&access.template AssurePool(), ids, shouldShrink); + return GetPool()->Add(id, p::Forward(value)); } - template - void ExcludeIdsWithoutStable(const AccessType& access, TArray& ids, - const bool shouldShrink = true) requires(sizeof...(C) > 1) + template + decltype(auto) Add(Id id, const C& value) const requires(IsSame>) { - (ExcludeIdsWithoutStable(access, ids, shouldShrink), ...); + return GetPool()->Add(id, value); } - - /** Find ids containing a component from a list 'source' into 'results'. */ - template - void FindIdsWith(const AccessType& access, const TView& source, TArray& results) + // Add component to an entities (if they dont have it already) + template + void Add(Id id) const requires((IsSame> && ...) && sizeof...(C) > 1) { - FindIdsWith(&access.template AssurePool(), source, results); + (Add(id), ...); } - template - void FindIdsWith(const AccessType& access, const TView& source, TArray& results) - requires(sizeof...(C) > 1) + + // Add component to many entities (if they dont have it already) + template + decltype(auto) AddN(TView ids, const C& value = {}) const { - FindIdsWith({&access.template AssurePool()...}, source, results); + return GetPool()->Add(ids.begin(), ids.end(), value); } - template - TArray FindIdsWith(const AccessType& access, const TView& source) + // Add components to many entities (if they dont have it already) + template + void AddN(TView ids) const requires((IsSame> && ...) && sizeof...(C) > 1) { - TArray results; - FindIdsWith(access, source, results); - return Move(results); + (AddN(ids), ...); } - /** Find ids NOT containing a component from a list 'source' into 'results'. */ - template - void FindIdsWithout(const AccessType& access, const TArray& source, TArray& results) + + template + void Remove(const Id id) const requires(IsSame>) { - FindIdsWithout(&access.template AssurePool(), source, results); + if (auto* pool = GetPool()) + { + pool->Remove(id); + } } - template - TArray FindIdsWithout(const AccessType& access, const TArray& source) + template + void Remove(const Id id) const requires(sizeof...(C) > 1) { - TArray results; - FindIdsWithout(access, source, results); - return Move(results); + (Remove(id), ...); } - - /** - * Find and remove ids containing a component from list 'source' into 'results'. - * Does not guarantee order. - */ - template - void ExtractIdsWith(const AccessType& access, TArray& source, TArray& results, - const bool shouldShrink = true) + template + void Remove(TView ids) const requires(IsSame>) { - ExtractIdsWith(&access.template AssurePool(), source, results); + if (auto* pool = GetPool()) + { + pool->Remove(ids); + } } - template - TArray ExtractIdsWith( - const AccessType& access, TArray& source, const bool shouldShrink = true) + template + void Remove(TView ids) const requires(sizeof...(C) > 1) { - TArray results; - ExtractIdsWith(access, source, results); - return Move(results); + (Remove(ids), ...); } - /** - * Find and remove ids containing a component from list 'source' into 'results'. - * Guarantees order. - */ - template - void ExtractIdsWithStable(const AccessType& access, TArray& source, TArray& results, - const bool shouldShrink = true) + template + C& Get(Id id) const { - ExtractIdsWithStable(&access.template AssurePool(), source, results); - } - template - TArray ExtractIdsWithStable( - const AccessType& access, TArray& source, const bool shouldShrink = true) - { - TArray results; - ExtractIdsWithStable(access, source, results); - return Move(results); + return GetPool()->Get(id); } - /** - * Find and remove ids containing a component from list 'source' into 'results'. - * Does not guarantee order. - */ - template - void ExtractIdsWithout(const AccessType& access, TArray& source, TArray& results, - const bool shouldShrink = true) + template + C* TryGet(Id id) const { - ExtractIdsWithout(&access.template AssurePool(), source, results); + return GetPool()->TryGet(id); } - template - TArray ExtractIdsWithout( - const AccessType& access, TArray& source, const bool shouldShrink = true) + + template + C& GetOrAdd(Id id) const requires(IsMutable) { - TArray results; - ExtractIdsWithout(access, source, results); - return Move(results); + return GetPool()->GetOrAdd(id); } - /** - * Find and remove ids not containing a component from list 'source' into 'results'. - * Guarantees order. - */ - template - void ExtractIdsWithoutStable(const AccessType& access, TArray& source, TArray& results, - const bool shouldShrink = true) + i32 Size() const { - ExtractIdsWithoutStable(&access.template AssurePool(), source, results); + static_assert(sizeof...(T) == 1, "Can only get the size of single component accesses"); + return GetPool()->Size(); } - template - TArray ExtractIdsWithoutStable( - const AccessType& access, TArray& source, const bool shouldShrink = true) + + template + i32 Size() const { - TArray results; - ExtractIdsWithoutStable(access, source, results); - return Move(results); + return GetPool()->Size(); } - - /** - * Find all ids containing all of the components - * - * @param access from where to access pools - * @param ids array where matching ids will be added - * @see FindAllIdsWithAny() - */ - template - void FindAllIdsWith(const AccessType& access, TArray& ids) requires(sizeof...(C) >= 1) + EntityContext& GetContext() const { - FindAllIdsWith({&access.template AssurePool()...}, ids); + return context; } - /** - * Find all ids containing all of the components - * - * @param access from where to access pools - * @return ids array with matching ids - * @see FindAllIdsWithAny() - */ - template - TArray FindAllIdsWith(const AccessType& access) requires(sizeof...(C) >= 1) + template + static constexpr bool HasType() { - TArray ids; - FindAllIdsWith(access, ids); - return Move(ids); + return ListContains>(); } - /** - * Find all ids containing any of the components. - * Includes possible duplicates - * - * @param access from where to access pools - * @param ids array where matching ids will be added - * @see FindAllIdsWith() - */ - template - void FindAllIdsWithAny(const AccessType& access, TArray& ids) requires(sizeof...(C) >= 1) + template + static constexpr bool IsReadable() { - FindAllIdsWithAny({&access.template AssurePool()...}, ids); + return HasType(); } - /** - * Find all ids containing any of the components. - * Prevents duplicates - * - * @param access from where to access pools - * @param ids array where matching ids will be added - * @see FindAllIdsWithAnyUnique() - */ - template - void FindAllIdsWithAnyUnique(const AccessType& access, TArray& ids) - requires(sizeof...(C) >= 1) + template + static constexpr bool IsWritable() { - FindAllIdsWithAnyUnique({&access.template AssurePool()...}, ids); + return IsMutable && ListContains>>(); } +}; - /** - * Find all ids containing any of the components. - * Includes possible duplicates - * - * @param access from where to access pools - * @return ids array with matching ids - * @see FindAllIdsWith() - */ - template - TArray FindAllIdsWithAny(const AccessType& access) requires(sizeof...(C) >= 1) +template +using TAccessRef = const TAccess&; + +struct Access +{ +protected: + + EntityContext& ast; + TArray types; + TArray pools; + + +public: + Access(EntityContext& ast, const TArray& types) : ast{ast} {} + + template + Access(TAccessRef access) : ast{access.ast} + {} + + template + TPool>* GetPool() const requires(IsMutable) { - TArray ids; - FindAllIdsWithAny(access, ids); - return Move(ids); + return nullptr; } - /** - * Find all ids containing any of the components. - * Prevents duplicates - * - * @param access from where to access pools - * @return ids array with matching ids - * @see FindAllIdsWithAny() - */ - template - TArray FindAllIdsWithAnyUnique(const AccessType& access) requires(sizeof...(C) >= 1) + template + const TPool>* GetPool() const requires(IsConst) { - TArray ids; - FindAllIdsWithAnyUnique(access, ids); - return Move(ids); + return nullptr; } - template - Id GetFirstId(const AccessType& access) +private: + + i32 GetPoolIndex() const { - if constexpr (sizeof...(C) == 1) // Only one component? - { - const BasePool* pool = access.template GetPool(); - if (pool && pool->Size() > 0) - { - return *pool->begin(); - } - return NoId; - } - else - { - TArray ids; - FindAllIdsWith(access, ids); - return ids.IsEmpty() ? NoId : ids[0]; - } + return 0; } +}; - //////////////////////////////// - // HIERARCHY - // +//////////////////////////////// +// FILTERING +// - struct PIPE_API CParent - { - P_STRUCT(CParent) +/** Remove ids containing a component from 'ids'. Does not guarantee order. */ +P_API void ExcludeIdsWith(const BasePool* pool, TArray& ids, const bool shouldShrink = true); - P_PROP(children, PF_Edit) - TArray children; - }; - PIPE_API void Read(Reader& ct, CParent& val); - PIPE_API void Write(Writer& ct, const CParent& val); +/** Remove ids containing a component from 'ids'. Guarantees order. */ +P_API void ExcludeIdsWithStable( + const BasePool* pool, TArray& ids, const bool shouldShrink = true); + +/** Remove ids NOT containing a component from 'ids'. Does not guarantee order. */ +P_API void ExcludeIdsWithout(const BasePool* pool, TArray& ids, const bool shouldShrink = true); + +/** Remove ids NOT containing a component from 'ids'. Guarantees order. */ +P_API void ExcludeIdsWithoutStable( + const BasePool* pool, TArray& ids, const bool shouldShrink = true); + + +/** Find ids containing a component from a list 'source' into 'results'. */ +P_API void FindIdsWith(const BasePool* pool, TView source, TArray& results); +P_API void FindIdsWith( + TView pools, TView source, TArray& results); + +/** Find ids NOT containing a component from a list 'source' into 'results'. */ +P_API void FindIdsWithout(const BasePool* pool, TView source, TArray& results); + + +/** + * Find and remove ids containing a component from list 'source' into 'results'. + * Does not guarantee order. + */ +P_API void ExtractIdsWith( + const BasePool* pool, TArray& source, TArray& results, const bool shouldShrink = true); + +/** + * Find and remove ids containing a component from list 'source' into 'results'. + * Guarantees order. + */ +P_API void ExtractIdsWithStable( + const BasePool* pool, TArray& source, TArray& results, const bool shouldShrink = true); + +/** + * Find and remove ids containing a component from list 'source' into 'results'. + * Does not guarantee order. + */ +P_API void ExtractIdsWithout( + const BasePool* pool, TArray& source, TArray& results, const bool shouldShrink = true); + +/** + * Find and remove ids not containing a component from list 'source' into 'results'. + * Guarantees order. + */ +P_API void ExtractIdsWithoutStable( + const BasePool* pool, TArray& source, TArray& results, const bool shouldShrink = true); + + +/** Find all ids containing all of the components */ +P_API void FindAllIdsWith(TView pools, TArray& ids); - struct PIPE_API CChild +/** Find all ids containing any of the components. Includes possible duplicates */ +P_API void FindAllIdsWithAny(TView pools, TArray& ids); + +/** Find all ids containing any of the components. Prevents duplicates */ +P_API void FindAllIdsWithAnyUnique(TView pools, TArray& ids); + + +// Templated API + +/** + * Remove ids containing a component from 'ids'. Does not guarantee order. + * + * @param access from where to access pools + * @param ids array that will be modified + * @param shouldShrink if true, the ids array will be shrink at the end + * @see ExcludeWithStable(), ExcludeWithout() + */ +template +void ExcludeIdsWith(const AccessType& access, TArray& ids, const bool shouldShrink = true) +{ + ExcludeIdsWith(&access.template AssurePool(), ids, shouldShrink); +} +template +void ExcludeIdsWith(const AccessType& access, TArray& ids, const bool shouldShrink = true) + requires(sizeof...(C) > 1) +{ + (ExcludeIdsWith(access, ids, shouldShrink), ...); +} + +template +void ExcludeIdsWith(TArray& ids, Predicate predicate, const bool shouldShrink = true) +{ + for (i32 i = ids.Size() - 1; i >= 0; --i) + { + if (predicate(ids[i])) + { + ids.RemoveAtSwapUnsafe(i); + } + } + if (shouldShrink) { - P_STRUCT(CChild) + ids.Shrink(); + } +} + +/** + * Remove ids containing a component from 'ids'. Guarantees order. + * + * @param access from where to access pools + * @param ids array that will be modified + * @param shouldShrink if true, the ids array will be shrink at the end + * @see ExcludeWith(), ExcludeWithoutStable() + */ +template +void ExcludeIdsWithStable(const AccessType& access, TArray& ids, const bool shouldShrink = true) +{ + ExcludeIdsWithStable(&access.template AssurePool(), ids, shouldShrink); +} +template +void ExcludeIdsWithStable(const AccessType& access, TArray& ids, const bool shouldShrink = true) + requires(sizeof...(C) > 1) +{ + (ExcludeIdsWithStable(access, ids, shouldShrink), ...); +} + +/** + * Remove ids NOT containing a component from 'ids'. Does not guarantee order. + * + * @param access from where to access pools + * @param ids array that will be modified + * @param shouldShrink if true, the ids array will be shrink at the end + * @see ExcludeWithoutStable(), ExcludeWith() + */ +template +void ExcludeIdsWithout(const AccessType& access, TArray& ids, const bool shouldShrink = true) +{ + ExcludeIdsWithout(&access.template AssurePool(), ids, shouldShrink); +} - P_PROP(parent, PF_Edit) - Id parent = NoId; - }; - PIPE_API void Read(Reader& ct, CChild& val); - PIPE_API void Write(Writer& ct, const CChild& val); - - - // Link a list of nodes at the end of the parent children list - PIPE_API void AttachId( - TAccessRef, TWrite> access, Id parent, TView children); - // Link a list of nodes after prevChild in the list of children nodes - PIPE_API void AttachIdAfter(TAccessRef, TWrite> access, Id parent, - TView childrenIds, Id prevChild); - PIPE_API void TransferIdChildren(TAccessRef, TWrite> access, - TView childrenIds, Id destination); - // TODO: void TransferAllChildren(Tree& ast, Id origin, Id destination); - PIPE_API void DetachIdParent(TAccessRef, TWrite> access, - TView childrenIds, bool keepComponents); - PIPE_API void DetachIdChildren(TAccessRef, TWrite> access, - TView parents, bool keepComponents = false); - - /** Obtain direct children ids from the provided parent Id. Examples: - * - Children of A (where A->B->C) is B. - * - Children of A (where A->B, A->C) are B and C. - */ - PIPE_API const TArray* GetIdChildren(TAccessRef access, Id node); +template +void ExcludeIdsWithout(const AccessType& access, TArray& ids, const bool shouldShrink = true) + requires(sizeof...(C) > 1) +{ + (ExcludeIdsWithout(access, ids, shouldShrink), ...); +} + +/** + * Remove ids NOT containing a component from 'ids'. Guarantees order. + * + * @param access from where to access pools + * @param ids array that will be modified + * @param shouldShrink if true, the ids array will be shrink at the end + * @see ExcludeWithout(), ExcludeWithStable() + */ +template +void ExcludeIdsWithoutStable( + const AccessType& access, TArray& ids, const bool shouldShrink = true) +{ + ExcludeIdsWithoutStable(&access.template AssurePool(), ids, shouldShrink); +} +template +void ExcludeIdsWithoutStable(const AccessType& access, TArray& ids, + const bool shouldShrink = true) requires(sizeof...(C) > 1) +{ + (ExcludeIdsWithoutStable(access, ids, shouldShrink), ...); +} - /** Obtain direct children ids from the provided parent Ids. Examples: - * - Children of A (where A->B->C) is B. - * - Children of A (where A->B, A->C) are B and C. - * - Children of A and B (where A->B->C) are B, C. - */ - PIPE_API void GetIdChildren( - TAccessRef access, TView nodes, TArray& outChildrenIds); - /** Obtain all children ids from the provided parent Ids. Examples: - * - All children of A (where A->B->C) are B and C. - * - All children of A and D (where A->B->C, D->E->F) are B, C, E and F. - * - All children of A and B (where A->B->C->D) are B, C, D, C, D (duplicates are not handled). - */ - PIPE_API void GetAllIdChildren(TAccessRef access, TView parentIds, - TArray& outChildrenIds, u32 depth = 1); +/** Find ids containing a component from a list 'source' into 'results'. */ +template +void FindIdsWith(const AccessType& access, const TView& source, TArray& results) +{ + FindIdsWith(&access.template AssurePool(), source, results); +} +template +void FindIdsWith(const AccessType& access, const TView& source, TArray& results) + requires(sizeof...(C) > 1) +{ + FindIdsWith({&access.template AssurePool()...}, source, results); +} - PIPE_API Id GetIdParent(TAccessRef access, Id childId); - PIPE_API void GetIdParent( - TAccessRef access, TView childrenIds, TArray& outParents); - PIPE_API void GetAllIdParents( - TAccessRef access, TView childrenIds, TArray& outParents); +template +TArray FindIdsWith(const AccessType& access, const TView& source) +{ + TArray results; + FindIdsWith(access, source, results); + return Move(results); +} + +/** Find ids NOT containing a component from a list 'source' into 'results'. */ +template +void FindIdsWithout(const AccessType& access, const TArray& source, TArray& results) +{ + FindIdsWithout(&access.template AssurePool(), source, results); +} +template +TArray FindIdsWithout(const AccessType& access, const TArray& source) +{ + TArray results; + FindIdsWithout(access, source, results); + return Move(results); +} + +/** + * Find and remove ids containing a component from list 'source' into 'results'. + * Does not guarantee order. + */ +template +void ExtractIdsWith(const AccessType& access, TArray& source, TArray& results, + const bool shouldShrink = true) +{ + ExtractIdsWith(&access.template AssurePool(), source, results); +} +template +TArray ExtractIdsWith( + const AccessType& access, TArray& source, const bool shouldShrink = true) +{ + TArray results; + ExtractIdsWith(access, source, results); + return Move(results); +} + +/** + * Find and remove ids containing a component from list 'source' into 'results'. + * Guarantees order. + */ +template +void ExtractIdsWithStable(const AccessType& access, TArray& source, TArray& results, + const bool shouldShrink = true) +{ + ExtractIdsWithStable(&access.template AssurePool(), source, results); +} +template +TArray ExtractIdsWithStable( + const AccessType& access, TArray& source, const bool shouldShrink = true) +{ + TArray results; + ExtractIdsWithStable(access, source, results); + return Move(results); +} + +/** + * Find and remove ids containing a component from list 'source' into 'results'. + * Does not guarantee order. + */ +template +void ExtractIdsWithout(const AccessType& access, TArray& source, TArray& results, + const bool shouldShrink = true) +{ + ExtractIdsWithout(&access.template AssurePool(), source, results); +} +template +TArray ExtractIdsWithout( + const AccessType& access, TArray& source, const bool shouldShrink = true) +{ + TArray results; + ExtractIdsWithout(access, source, results); + return Move(results); +} + +/** + * Find and remove ids not containing a component from list 'source' into 'results'. + * Guarantees order. + */ +template +void ExtractIdsWithoutStable(const AccessType& access, TArray& source, TArray& results, + const bool shouldShrink = true) +{ + ExtractIdsWithoutStable(&access.template AssurePool(), source, results); +} +template +TArray ExtractIdsWithoutStable( + const AccessType& access, TArray& source, const bool shouldShrink = true) +{ + TArray results; + ExtractIdsWithoutStable(access, source, results); + return Move(results); +} + + +/** + * Find all ids containing all of the components + * + * @param access from where to access pools + * @param ids array where matching ids will be added + * @see FindAllIdsWithAny() + */ +template +void FindAllIdsWith(const AccessType& access, TArray& ids) requires(sizeof...(C) >= 1) +{ + FindAllIdsWith({&access.template AssurePool()...}, ids); +} + + +/** + * Find all ids containing all of the components + * + * @param access from where to access pools + * @return ids array with matching ids + * @see FindAllIdsWithAny() + */ +template +TArray FindAllIdsWith(const AccessType& access) requires(sizeof...(C) >= 1) +{ + TArray ids; + FindAllIdsWith(access, ids); + return Move(ids); +} + +/** + * Find all ids containing any of the components. + * Includes possible duplicates + * + * @param access from where to access pools + * @param ids array where matching ids will be added + * @see FindAllIdsWith() + */ +template +void FindAllIdsWithAny(const AccessType& access, TArray& ids) requires(sizeof...(C) >= 1) +{ + FindAllIdsWithAny({&access.template AssurePool()...}, ids); +} + +/** + * Find all ids containing any of the components. + * Prevents duplicates + * + * @param access from where to access pools + * @param ids array where matching ids will be added + * @see FindAllIdsWithAnyUnique() + */ +template +void FindAllIdsWithAnyUnique(const AccessType& access, TArray& ids) requires(sizeof...(C) >= 1) +{ + FindAllIdsWithAnyUnique({&access.template AssurePool()...}, ids); +} + +/** + * Find all ids containing any of the components. + * Includes possible duplicates + * + * @param access from where to access pools + * @return ids array with matching ids + * @see FindAllIdsWith() + */ +template +TArray FindAllIdsWithAny(const AccessType& access) requires(sizeof...(C) >= 1) +{ + TArray ids; + FindAllIdsWithAny(access, ids); + return Move(ids); +} + +/** + * Find all ids containing any of the components. + * Prevents duplicates + * + * @param access from where to access pools + * @return ids array with matching ids + * @see FindAllIdsWithAny() + */ +template +TArray FindAllIdsWithAnyUnique(const AccessType& access) requires(sizeof...(C) >= 1) +{ + TArray ids; + FindAllIdsWithAnyUnique(access, ids); + return Move(ids); +} - /** - * Find a parent id matching a delegate - */ - PIPE_API Id FindIdParent( - TAccessRef access, Id child, const TFunction& callback); - PIPE_API void FindIdParents(TAccessRef access, TView childrenIds, - TArray& outParents, const TFunction& callback); +template +Id GetFirstId(const AccessType& access) +{ + if constexpr (sizeof...(C) == 1) // Only one component? + { + const BasePool* pool = access.template GetPool(); + if (pool && pool->Size() > 0) + { + return *pool->begin(); + } + return NoId; + } + else + { + TArray ids; + FindAllIdsWith(access, ids); + return ids.IsEmpty() ? NoId : ids[0]; + } +} - // void Copy(Tree& ast, t TArray& nodes, TArray& outNewNodes); - // void CopyDeep(Tree& ast, const TArray& rootNodes, TArray& outNewRootNodes); - // void CopyAndTransferAllChildrenDeep(Tree& ast, Id root, Id otherRoot); - PIPE_API void RemoveId( - TAccessRef, TWrite> access, TView nodes, bool deep = false); +//////////////////////////////// +// HIERARCHY +// - /** - * Iterates children nodes making sure child->parent links are correct or fixed - * Only first depth links are affected - * Complexity: O(N) - * @parents: where to look for children to fix up - * @return true if an incorrect link was found and fixed - */ - PIPE_API bool FixParentIdLinks(TAccessRef, CParent> access, TView parents); +struct P_API CParent +{ + P_STRUCT(CParent) - /** - * Iterates children nodes looking for invalid child->parent links - * Only first depth links are affected - * Complexity: O(1) <-> O(N) (First invalid link makes an early out) - * @parents: where to look for children - * @return true if an incorrect link was found - */ - PIPE_API bool ValidateParentIdLinks(TAccessRef access, TView parents); + P_PROP(children, PF_Edit) + TArray children; +}; +P_API void Read(Reader& ct, CParent& val); +P_API void Write(Writer& ct, const CParent& val); + +struct P_API CChild +{ + P_STRUCT(CChild) - PIPE_API void GetRootIds(TAccessRef access, TArray& outRoots); + P_PROP(parent, PF_Edit) + Id parent = NoId; +}; +P_API void Read(Reader& ct, CChild& val); +P_API void Write(Writer& ct, const CChild& val); + + +// Link a list of nodes at the end of the parent children list +P_API void AttachId( + TAccessRef, TWrite> access, Id parent, TView children); +// Link a list of nodes after prevChild in the list of children nodes +P_API void AttachIdAfter(TAccessRef, TWrite> access, Id parent, + TView childrenIds, Id prevChild); +P_API void TransferIdChildren(TAccessRef, TWrite> access, + TView childrenIds, Id destination); +// TODO: void TransferAllChildren(Tree& ast, Id origin, Id destination); +P_API void DetachIdParent(TAccessRef, TWrite> access, + TView childrenIds, bool keepComponents); +P_API void DetachIdChildren(TAccessRef, TWrite> access, + TView parents, bool keepComponents = false); + +/** Obtain direct children ids from the provided parent Id. Examples: + * - Children of A (where A->B->C) is B. + * - Children of A (where A->B, A->C) are B and C. + */ +P_API const TArray* GetIdChildren(TAccessRef access, Id node); + +/** Obtain direct children ids from the provided parent Ids. Examples: + * - Children of A (where A->B->C) is B. + * - Children of A (where A->B, A->C) are B and C. + * - Children of A and B (where A->B->C) are B, C. + */ +P_API void GetIdChildren( + TAccessRef access, TView nodes, TArray& outChildrenIds); + +/** Obtain all children ids from the provided parent Ids. Examples: + * - All children of A (where A->B->C) are B and C. + * - All children of A and D (where A->B->C, D->E->F) are B, C, E and F. + * - All children of A and B (where A->B->C->D) are B, C, D, C, D (duplicates are not handled). + */ +P_API void GetAllIdChildren(TAccessRef access, TView parentIds, + TArray& outChildrenIds, u32 depth = 1); + +P_API Id GetIdParent(TAccessRef access, Id childId); +P_API void GetIdParent( + TAccessRef access, TView childrenIds, TArray& outParents); +P_API void GetAllIdParents( + TAccessRef access, TView childrenIds, TArray& outParents); + +/** + * Find a parent id matching a delegate + */ +P_API Id FindIdParent(TAccessRef access, Id child, const TFunction& callback); +P_API void FindIdParents(TAccessRef access, TView childrenIds, + TArray& outParents, const TFunction& callback); + +// void Copy(Tree& ast, t TArray& nodes, TArray& outNewNodes); +// void CopyDeep(Tree& ast, const TArray& rootNodes, TArray& outNewRootNodes); +// void CopyAndTransferAllChildrenDeep(Tree& ast, Id root, Id otherRoot); + +P_API void RemoveId( + TAccessRef, TWrite> access, TView nodes, bool deep = false); + +/** + * Iterates children nodes making sure child->parent links are correct or fixed + * Only first depth links are affected + * Complexity: O(N) + * @parents: where to look for children to fix up + * @return true if an incorrect link was found and fixed + */ +P_API bool FixParentIdLinks(TAccessRef, CParent> access, TView parents); + +/** + * Iterates children nodes looking for invalid child->parent links + * Only first depth links are affected + * Complexity: O(1) <-> O(N) (First invalid link makes an early out) + * @parents: where to look for children + * @return true if an incorrect link was found + */ +P_API bool ValidateParentIdLinks(TAccessRef access, TView parents); + +P_API void GetRootIds(TAccessRef access, TArray& outRoots); - //////////////////////////////// - // DEFINITIONS - // +//////////////////////////////// +// DEFINITIONS +// - template - void IdRegistry::Each(Callback cb) const +template +void IdRegistry::Each(Callback cb) const +{ + if (available.IsEmpty()) { - if (available.IsEmpty()) + for (i32 i = 0; i < entities.Size(); ++i) { - for (i32 i = 0; i < entities.Size(); ++i) - { - cb(entities[i]); - } + cb(entities[i]); } - else + } + else + { + for (i32 i = 0; i < entities.Size(); ++i) { - for (i32 i = 0; i < entities.Size(); ++i) + const Id id = entities[i]; + if (GetIdIndex(id) == i) { - const Id id = entities[i]; - if (GetIdIndex(id) == i) - { - cb(id); - } + cb(id); } } } +} - template - inline void EntityReader::SerializePool() +template +inline void EntityReader::SerializePool() +{ + if (EnterNext(GetTypeName(false))) { - if (EnterNext(GetTypeName(false))) - { - auto& pool = GetContext().AssurePool(); + auto& pool = GetContext().AssurePool(); - if (serializingMany) [[likely]] + if (serializingMany) [[likely]] + { + String key; + BeginObject(); + for (i32 i = 0; i < ids.Size(); ++i) { - String key; - BeginObject(); - for (i32 i = 0; i < ids.Size(); ++i) - { - const Id node = ids[i]; - key.clear(); - Strings::FormatTo(key, "{}", i); + const Id node = ids[i]; + key.clear(); + Strings::FormatTo(key, "{}", i); - if (EnterNext(key)) + if (EnterNext(key)) + { + if constexpr (!IsEmpty) { - if constexpr (!IsEmpty) - { - T& comp = pool.GetOrAdd(node); - Serialize(comp); - } - else - { - pool.Add(node); - } - Leave(); + T& comp = pool.GetOrAdd(node); + Serialize(comp); + } + else + { + pool.Add(node); } + Leave(); } } + } + else + { + if constexpr (!IsEmpty) + { + T& comp = pool.GetOrAdd(ids[0]); + Serialize(comp); + } else { - if constexpr (!IsEmpty) - { - T& comp = pool.GetOrAdd(ids[0]); - Serialize(comp); - } - else - { - pool.Add(ids[0]); - } + pool.Add(ids[0]); } - Leave(); } + Leave(); } +} - template - inline void EntityWriter::SerializePool() - { - TArray> componentIds; // TODO: Make sure this is needed +template +inline void EntityWriter::SerializePool() +{ + TArray> componentIds; // TODO: Make sure this is needed - auto* pool = context.GetPool(); - if (pool) + auto* pool = context.GetPool(); + if (pool) + { + componentIds.Reserve(Min(i32(pool->Size()), ids.Size())); + for (i32 i = 0; i < ids.Size(); ++i) { - componentIds.Reserve(Min(i32(pool->Size()), ids.Size())); - for (i32 i = 0; i < ids.Size(); ++i) + const Id id = ids[i]; + if (pool->Has(id)) { - const Id id = ids[i]; - if (pool->Has(id)) - { - componentIds.Add({i, id}); - } + componentIds.Add({i, id}); } } + } - if (componentIds.IsEmpty()) - { - return; - } + if (componentIds.IsEmpty()) + { + return; + } - // FIX: yyjson doesn't seem to take into account stringview length when generating text - // Temporarely fixed by caching component name keys - PushAddFlags(p::WriteFlags_CacheStringKeys); - if (EnterNext(GetTypeName(false))) + // FIX: yyjson doesn't seem to take into account stringview length when generating text + // Temporarely fixed by caching component name keys + PushAddFlags(p::WriteFlags_CacheStringKeys); + if (EnterNext(GetTypeName(false))) + { + if (serializingMany) [[likely]] { - if (serializingMany) [[likely]] + String key; + BeginObject(); + for (auto id : componentIds) { - String key; - BeginObject(); - for (auto id : componentIds) - { - key.clear(); - Strings::FormatTo(key, "{}", id.first); + key.clear(); + Strings::FormatTo(key, "{}", id.first); - if constexpr (std::is_empty_v) - { - Next(StringView{key}, T{}); - } - else - { - Next(StringView{key}, pool->Get(id.second)); - } - } - } - else - { if constexpr (std::is_empty_v) { - Serialize(T{}); + Next(StringView{key}, T{}); } else { - Serialize(pool->Get(componentIds.First().second)); + Next(StringView{key}, pool->Get(id.second)); } } - Leave(); } - PopFlags(); - } - - - template - inline TPool>& EntityContext::AssurePool() const - { - const TypeId componentId = RegisterTypeId>(); - - i32 index = pools.LowerBound(PoolInstance{componentId, {}}); - if (index != NO_INDEX) + else { - if (componentId != pools[index].GetId()) + if constexpr (std::is_empty_v) { - pools.Insert(index, CreatePoolInstance()); + Serialize(T{}); + } + else + { + Serialize(pool->Get(componentIds.First().second)); } } - else - { - index = pools.Add(CreatePoolInstance()); - } - - BasePool* pool = pools[index].GetPool(); - return *static_cast>*>(pool); + Leave(); } + PopFlags(); +} - template - inline PoolInstance EntityContext::CreatePoolInstance() const - { - constexpr TypeId componentId = GetTypeId>(); - auto& self = const_cast(*this); - PoolInstance instance{componentId, MakeUnique>>(self)}; - return Move(instance); - } +template +inline TPool>& EntityContext::AssurePool() const +{ + const TypeId componentId = RegisterTypeId>(); - template - inline Static& EntityContext::SetStatic() + i32 index = pools.LowerBound(PoolInstance{componentId, {}}); + if (index != NO_INDEX) { - OwnPtr& ptr = FindOrAddStaticPtr(statics, GetTypeId()); - ptr = MakeOwned(); - return *ptr.GetUnsafe(); + if (componentId != pools[index].GetId()) + { + pools.Insert(index, CreatePoolInstance()); + } } - - template - inline Static& EntityContext::SetStatic(Static&& value) + else { - OwnPtr& ptr = FindOrAddStaticPtr(statics, GetTypeId()); - ptr = MakeOwned(p::Forward(value)); - return *ptr.GetUnsafe(); + index = pools.Add(CreatePoolInstance()); } - template - inline Static& EntityContext::SetStatic(const Static& value) - { - OwnPtr& ptr = FindOrAddStaticPtr(statics, GetTypeId()); - ptr = MakeOwned(value); - return *ptr.GetUnsafe(); - } + BasePool* pool = pools[index].GetPool(); + return *static_cast>*>(pool); +} - template - inline Static& EntityContext::GetOrSetStatic() +template +inline PoolInstance EntityContext::CreatePoolInstance() const +{ + constexpr TypeId componentId = GetTypeId>(); + + auto& self = const_cast(*this); + PoolInstance instance{componentId, MakeUnique>>(self)}; + return Move(instance); +} + +template +inline Static& EntityContext::SetStatic() +{ + OwnPtr& ptr = FindOrAddStaticPtr(statics, GetTypeId()); + ptr = MakeOwned(); + return *ptr.GetUnsafe(); +} + +template +inline Static& EntityContext::SetStatic(Static&& value) +{ + OwnPtr& ptr = FindOrAddStaticPtr(statics, GetTypeId()); + ptr = MakeOwned(p::Forward(value)); + return *ptr.GetUnsafe(); +} + +template +inline Static& EntityContext::SetStatic(const Static& value) +{ + OwnPtr& ptr = FindOrAddStaticPtr(statics, GetTypeId()); + ptr = MakeOwned(value); + return *ptr.GetUnsafe(); +} + +template +inline Static& EntityContext::GetOrSetStatic() +{ + bool bAdded = false; + OwnPtr& ptr = FindOrAddStaticPtr(statics, GetTypeId(), &bAdded); + if (bAdded) { - bool bAdded = false; - OwnPtr& ptr = FindOrAddStaticPtr(statics, GetTypeId(), &bAdded); - if (bAdded) - { - ptr = MakeOwned(); - } - return *ptr.GetUnsafe(); + ptr = MakeOwned(); } + return *ptr.GetUnsafe(); +} - template - inline Static& EntityContext::GetOrSetStatic(Static&& value) +template +inline Static& EntityContext::GetOrSetStatic(Static&& value) +{ + bool bAdded = false; + OwnPtr& ptr = FindOrAddStaticPtr(statics, GetTypeId(), &bAdded); + if (bAdded) { - bool bAdded = false; - OwnPtr& ptr = FindOrAddStaticPtr(statics, GetTypeId(), &bAdded); - if (bAdded) - { - ptr = MakeOwned(Forward(value)); - } - return *ptr.GetUnsafe(); + ptr = MakeOwned(Forward(value)); } + return *ptr.GetUnsafe(); +} - template - inline Static& EntityContext::GetOrSetStatic(const Static& value) +template +inline Static& EntityContext::GetOrSetStatic(const Static& value) +{ + bool bAdded = false; + OwnPtr& ptr = FindOrAddStaticPtr(statics, GetTypeId(), &bAdded); + if (bAdded) { - bool bAdded = false; - OwnPtr& ptr = FindOrAddStaticPtr(statics, GetTypeId(), &bAdded); - if (bAdded) - { - ptr = MakeOwned(value); - } - return *ptr.GetUnsafe(); + ptr = MakeOwned(value); } + return *ptr.GetUnsafe(); +} } // namespace p template<> diff --git a/Include/PipeECSFwd.h b/Include/PipeECSFwd.h index 80bb5834..d5b86471 100644 --- a/Include/PipeECSFwd.h +++ b/Include/PipeECSFwd.h @@ -1,7 +1,7 @@ // Copyright 2015-2024 Piperift - All rights reserved #pragma once -#include "Pipe/Core/Platform.h" +#include "PipePlatform.h" namespace p::ecs diff --git a/Include/PipeFiles.h b/Include/PipeFiles.h index e32d243e..51075175 100644 --- a/Include/PipeFiles.h +++ b/Include/PipeFiles.h @@ -17,11 +17,11 @@ namespace p * Selects a file using a system dialog. * @param title of the dialog * @param defaultPath where to open the dialog - * @param alwaysShowDefaultPath if true dialog will open to default path. Else, the system - * can decide to, for example, display last user folder (windows). + * @param alwaysShowDefaultPath if true dialog will open to default path. Else, + * the system can decide to, for example, display last user folder (windows). * @return selected file path */ - PIPE_API String SelectFileDialog(StringView title, StringView defaultPath, + P_API String SelectFileDialog(StringView title, StringView defaultPath, const TArray& filters = { {"All Files", "*"} @@ -33,11 +33,10 @@ namespace p * @param title of the dialog * @param defaultPath where to open the dialog * @param outFiles selected by the user - * @param alwaysShowDefaultPath if true dialog will open to default path. Else, the system - * can decide to, for example, display last user folder (windows). + * @param alwaysShowDefaultPath if true dialog will open to default path. Else, + * the system can decide to, for example, display last user folder (windows). */ - PIPE_API void SelectFilesDialog(StringView title, StringView defaultPath, - TArray& outFiles, + P_API void SelectFilesDialog(StringView title, StringView defaultPath, TArray& outFiles, const TArray& filters = { {"All Files", "*"} @@ -48,22 +47,22 @@ namespace p * Selects a folder using a system dialog. * @param title of the dialog * @param defaultPath where to open the dialog - * @param bAlwaysShowDefaultPath if true dialog will open to default path. Else, the system - * can decide to, for example, display last user folder (windows). + * @param bAlwaysShowDefaultPath if true dialog will open to default path. Else, + * the system can decide to, for example, display last user folder (windows). * @return selected folder path */ - PIPE_API String SelectFolderDialog( + P_API String SelectFolderDialog( StringView title, StringView defaultPath, bool alwaysShowDefaultPath = false); /** * Selects a file path for a file to be saved using a system dialog. * @param title of the dialog * @param defaultPath where to open the dialog - * @param alwaysShowDefaultPath if true dialog will open to default path. Else, the system - * can decide to, for example, display last user folder (windows). + * @param alwaysShowDefaultPath if true dialog will open to default path. Else, + * the system can decide to, for example, display last user folder (windows). * @return selected file path */ - PIPE_API String SaveFileDialog(StringView title, StringView defaultPath, + P_API String SaveFileDialog(StringView title, StringView defaultPath, const TArray& filters = { {"All Files", "*"} @@ -71,7 +70,6 @@ namespace p bool alwaysShowDefaultPath = false, bool confirmOverwrite = false); #pragma endregion FileDialogs - #pragma region FileWatch enum class FileWatchAction : p::u8 { @@ -85,16 +83,16 @@ namespace p using FileWatchCallback = std::function; - struct PIPE_API FileWatcher + struct P_API FileWatcher { public: /** Should recursive watchers follow symbolic links? Default: false */ bool followsSymlinks = false; - /** Allow symlinks to watch recursively out of the pointed directory. Default: false. - * 'followsSymlinks' must be enabled. - * E.g: A symlink from '/home/folder' to '/'. With 'followsSymlinks=false' only '/home' and - * deeper are allowed. Set to false it will prevent infinite recursion. + /** Allow symlinks to watch recursively out of the pointed directory. Default: + * false. 'followsSymlinks' must be enabled. E.g: A symlink from + * '/home/folder' to '/'. With 'followsSymlinks=false' only '/home' and deeper + * are allowed. Set to false it will prevent infinite recursion. */ bool allowsOutOfScopeLinks = false; diff --git a/Include/PipeMath.h b/Include/PipeMath.h index fde5c04e..b4d267c8 100644 --- a/Include/PipeMath.h +++ b/Include/PipeMath.h @@ -3,8 +3,8 @@ #pragma once #include "Pipe/Core/Limits.h" -#include "Pipe/Core/Platform.h" #include "Pipe/Core/Utility.h" +#include "PipePlatform.h" #include @@ -58,23 +58,23 @@ namespace p /** Returns a random integer between 0 and RAND_MAX, inclusive */ - inline PIPE_API i32 Rand() + inline P_API i32 Rand() { return rand(); } /** Returns a random float between 0 and 1, inclusive. */ - inline PIPE_API float Rand01() + inline P_API float Rand01() { return (float)Rand() / (float)RAND_MAX; } - inline PIPE_API float Random(float min, float max) + inline P_API float Random(float min, float max) { return min + (Rand01() * (max - min)); } - inline PIPE_API i32 Random(i32 min, i32 max) + inline P_API i32 Random(i32 min, i32 max) { return min + (Rand01() * (max - min)); } @@ -128,12 +128,12 @@ namespace p return std::floor(v); } - PIPE_API constexpr i32 FloorToI32(float f) + P_API constexpr i32 FloorToI32(float f) { return i32(Floor(f)); } - PIPE_API constexpr i64 FloorToI64(double f) + P_API constexpr i64 FloorToI64(double f) { return i64(Floor(f)); } @@ -161,25 +161,25 @@ namespace p } return std::ceil(v); } - PIPE_API constexpr i32 CeilToI32(float f) + P_API constexpr i32 CeilToI32(float f) { return i32(Ceil(f)); } - PIPE_API constexpr i64 CeilToI64(double f) + P_API constexpr i64 CeilToI64(double f) { return i64(Ceil(f)); } - inline PIPE_API float Round(float f) + inline P_API float Round(float f) { return std::round(f); } - inline PIPE_API double Round(double f) + inline P_API double Round(double f) { return std::round(f); } - inline PIPE_API i32 RoundToInt(float f) + inline P_API i32 RoundToInt(float f) { return i32(Round(f)); } @@ -190,12 +190,12 @@ namespace p * @param f Floating point value to convert * @return The rounded integer */ - inline PIPE_API float RoundFromZero(float f) + inline P_API float RoundFromZero(float f) { return (f < 0.0f) ? Floor(f) : Ceil(f); } - inline PIPE_API double RoundFromZero(double d) + inline P_API double RoundFromZero(double d) { return (d < 0.0) ? Floor(d) : Ceil(d); } @@ -206,12 +206,12 @@ namespace p * @param v Floating point value to convert * @return The rounded integer */ - inline PIPE_API float RoundToZero(float f) + inline P_API float RoundToZero(float f) { return (f < 0.0f) ? Ceil(f) : Floor(f); } - inline PIPE_API double RoundToZero(double d) + inline P_API double RoundToZero(double d) { return (d < 0.0) ? Ceil(d) : Floor(d); } @@ -222,12 +222,12 @@ namespace p * @param F Floating point value to convert * @return The rounded integer */ - inline PIPE_API float RoundToNegativeInfinity(float f) + inline P_API float RoundToNegativeInfinity(float f) { return Floor(f); } - inline PIPE_API double RoundToNegativeInfinity(double d) + inline P_API double RoundToNegativeInfinity(double d) { return Floor(d); } @@ -235,19 +235,33 @@ namespace p /** * Converts a floating point number to an integer which is more positive: 0.1 becomes 1, * -0.1 becomes 0 - * @param F Floating point value to convert + * @param f Floating point value to convert * @return The rounded integer */ - inline PIPE_API float RoundToPositiveInfinity(float f) + inline P_API float RoundToPositiveInfinity(float f) { return Ceil(f); } - inline PIPE_API double RoundToPositiveInfinity(double d) + inline P_API double RoundToPositiveInfinity(double d) { return Ceil(d); } + /** + * Returns the fractional part of a float. + * @param f Floating point value to convert + * @return A float between >=0 and < 1. + */ + inline P_API float Frac(float f) + { + return f - p::Floor(f); + } + inline P_API double Frac(double d) + { + return d - p::Floor(d); + } + template static T Sqrt(T val) @@ -255,19 +269,19 @@ namespace p return std::sqrt(val); } - inline PIPE_API float InvSqrt(float x) + inline P_API float InvSqrt(float x) { return 1.f / Sqrt(x); } - inline PIPE_API double InvSqrt(double x) + inline P_API double InvSqrt(double x) { return 1. / Sqrt(x); } - inline PIPE_API float InvSqrt(i32 x) + inline P_API float InvSqrt(i32 x) { return 1.f / Sqrt(x); } - inline PIPE_API double InvSqrt(i64 x) + inline P_API double InvSqrt(i64 x) { return 1. / Sqrt(x); } @@ -279,25 +293,33 @@ namespace p } template - PIPE_API constexpr T Pow(T value, u32 power) + P_API constexpr T Pow(T value, u32 power) { if (power == 0) + { return value >= 0 ? 1 : -1; + } T result = value; for (u32 i = 1; i < power; ++i) + { result *= value; + } return result; } template - PIPE_API constexpr T Pow(T value, u32 power) + P_API constexpr T Pow(T value, u32 power) { if (power == 0) + { return 1u; + } T result = value; for (u32 i = 1; i < power; ++i) + { result *= value; + } return result; } @@ -307,11 +329,15 @@ namespace p if (std::is_constant_evaluated() && Integral

) { if (power == 0) + { return V(1); + } V result = value; for (P i = 1; i < power; ++i) + { result *= value; + } return result; } return std::pow(value, power); @@ -363,11 +389,11 @@ namespace p return a - b * (a / b); } - PIPE_API void SinCos(float value, float& outSin, float& outCos); - PIPE_API float Sin(float value); - PIPE_API float Cos(float value); + P_API void SinCos(float value, float& outSin, float& outCos); + P_API float Sin(float value); + P_API float Cos(float value); - PIPE_API float Atan2(float Y, float X); + P_API float Atan2(float Y, float X); /** * Computes the ASin of a scalar value. @@ -375,7 +401,7 @@ namespace p * @param Value input angle * @return ASin of Value */ - inline PIPE_API float FastAsin(float Value) + inline P_API float FastAsin(float Value) { // Note: We use FASTASIN_HALF_PI instead of HALF_PI inside of FastASin(), since it was // the value that accompanied the minimax coefficients below. It is important to use @@ -407,24 +433,24 @@ namespace p return (nonnegative ? fastAsinHalfPi - result : result - fastAsinHalfPi); } - inline PIPE_API bool NearlyEqual(float a, float b, float tolerance = smallNumber) + inline P_API bool NearlyEqual(float a, float b, float tolerance = smallNumber) { return Abs(b - a) <= tolerance; } - inline PIPE_API float Log(float k) + inline P_API float Log(float k) { return std::log(k); } - inline PIPE_API float Log(float k, float base) + inline P_API float Log(float k, float base) { return Log(k) / Log(base); } - inline PIPE_API double Log(double k) + inline P_API double Log(double k) { return std::log(k); } - inline PIPE_API double Log(double k, double base) + inline P_API double Log(double k, double base) { return Log(k) / Log(base); } @@ -434,11 +460,11 @@ namespace p return std::log(k); } - inline PIPE_API float Log2(float k) + inline P_API float Log2(float k) { return std::log2f(k); } - inline PIPE_API double Log2(double k) + inline P_API double Log2(double k) { return std::log2(k); } @@ -448,11 +474,11 @@ namespace p return std::log2(double(k)); } - inline PIPE_API const float Exp2(const float k) + inline P_API const float Exp2(const float k) { return std::exp2(k); } - inline PIPE_API const double Exp2(const double k) + inline P_API const double Exp2(const double k) { return std::exp2(k); } @@ -468,9 +494,9 @@ namespace p return (value & (value - 1)) == T(0); } - PIPE_API float ClampAngle(float a); + P_API float ClampAngle(float a); - PIPE_API float NormalizeAngle(float a); + P_API float NormalizeAngle(float a); - PIPE_API float ClampAngle(float a, float min, float max); + P_API float ClampAngle(float a, float min, float max); } // namespace p diff --git a/Include/PipeMemory.h b/Include/PipeMemory.h new file mode 100644 index 00000000..892051cb --- /dev/null +++ b/Include/PipeMemory.h @@ -0,0 +1,632 @@ +// Copyright 2015-2024 Piperift - All rights reserved + +#pragma once + +#include "Pipe/Core/Limits.h" +#include "Pipe/Core/TypeTraits.h" +#include "Pipe/Core/Utility.h" + + +namespace p +{ + + template + struct TInlineArray; + + +#pragma region Memory Ops + namespace Details + { + template + struct TIsCharacterByteOrBool : std::false_type + {}; + template<> + struct TIsCharacterByteOrBool : std::true_type + {}; // chars are characters + template<> + struct TIsCharacterByteOrBool : std::true_type + {}; // signed chars are also characters + template<> + struct TIsCharacterByteOrBool : std::true_type + {}; // unsigned chars are also characters + template<> + struct TIsCharacterByteOrBool : std::true_type + {}; + template<> + struct TIsCharacterByteOrBool : std::true_type + {}; + template<> + struct TIsCharacterByteOrBool : std::true_type + {}; + + template + struct TIsMemsetZeroConstructible + { + static constexpr bool value = + IsScalar && TIsCharacterByteOrBool::value && !IsVolatile; + }; + + template + struct TIsMemsetValueConstructible + { + static constexpr bool value = IsScalar && !IsVolatile && !IsMemberPointer; + }; + + template + bool AreBitsZero(const T& value) requires(IsScalar && !IsMemberPointer) + { + if constexpr (IsSame) + { + return true; + } + else + { + constexpr T zero{}; + return CmpMem(&value, &zero, sizeof(T)) == 0; + } + } + } // namespace Details + + namespace Memory + { + // Kilobyte, Megabyte, Gigabyte... + constexpr sizet B = 1; + constexpr sizet KB = 1024; + constexpr sizet MB = KB * 1024; + constexpr sizet GB = MB * 1024; + constexpr sizet TB = GB * 1024; + constexpr sizet PB = TB * 1024; + + // Kilobit, Megabit, Gigabit... + constexpr sizet Kb = KB / 8; + constexpr sizet Mb = MB / 8; + constexpr sizet Gb = GB / 8; + constexpr sizet Tb = TB / 8; + constexpr sizet Pb = PB / 8; + } // namespace Memory + + + P_API void MoveMem(void* dest, void* src, sizet size); + P_API void CopyMem(void* dest, const void* src, sizet size); + P_API void SwapMem(void* a, void* b, sizet size); + P_API void SetMem(void* dest, u8 value, sizet size); + P_API void SetZeroMem(void* dest, sizet size); + P_API i32 CmpMem(const void* a, const void* b, sizet size); + + /** + * @return the previous pointer of 'ptr' aligned to blockSize + */ + void* GetAlignedBlock(void* ptr, const sizet blockSize); + + /** + * @return the number of bytes needed for p to be aligned in 'align' + */ + P_API sizet GetAlignmentPadding(const void* p, sizet align); + + P_API sizet GetAlignmentPaddingWithHeader(const void* ptr, sizet align, sizet headerSize); + + + /** Constructs a number of contiguous items with the default constructor */ + template + constexpr void ConstructItems(T* data, sizet count) + { + if (!std::is_constant_evaluated() && Details::TIsMemsetZeroConstructible::value) + { + SetMem(data, 0, count * sizeof(T)); + } + else + { + const T* const end = data + count; + while (data < end) + { + new (data) T(); + ++data; + } + } + } + + /** Constructs a number of contiguous items by copying from a single source item */ + template + constexpr void ConstructItems(T* data, sizet count, const T& value) + { + if (!std::is_constant_evaluated()) + { + if constexpr (Details::TIsMemsetValueConstructible::value) + { + SetMem(data, static_cast(value), count * sizeof(T)); + return; + } + else if constexpr (Details::TIsMemsetZeroConstructible::value) + { + if (Details::AreBitsZero(value)) + { + SetMem(data, 0, count * sizeof(T)); + return; + } + } + } + + const T* const end = data + count; + while (data < end) + { + new (data) T(value); + ++data; + } + } + + /** Constructs a number of contiguous items by copying from source items */ + template + constexpr void CopyConstructItems(T* data, sizet count, const T* values) + { + if (!std::is_constant_evaluated() && IsTriviallyCopyConstructible) + { + CopyMem(data, values, count * sizeof(T)); + } + else + { + const T* const end = values + count; + while (values < end) + { + new (data) T(*values); + if constexpr (destroySourceInPlace) + { + values->T::~T(); + } + ++values; + ++data; + } + } + } + + template + constexpr void MoveConstructItems(T* data, sizet count, T* values) + { + if (!std::is_constant_evaluated() && IsTriviallyMoveConstructible) + { + MoveMem(data, values, count * sizeof(T)); + } + else + { + const T* const end = values + count; + while (values < end) + { + new (data) T(p::Forward(*values)); + if constexpr (destroySourceInPlace) + { + values->T::~T(); + } + ++values; + ++data; + } + } + } + + template + constexpr void MoveOrCopyConstructItems(T* data, sizet count, T* values) + { + if constexpr (IsMoveConstructible || !IsCopyConstructible) + { + MoveConstructItems(data, count, values); + } + else + { + CopyConstructItems(data, count, values); + } + } + + template + constexpr void CopyItems(T* dest, sizet count, const T* src) + { + if (!std::is_constant_evaluated() && IsTriviallyCopyAssignable) + { + CopyMem(dest, src, count * sizeof(T)); + } + else + { + const T* const end = src + count; + while (src < end) + { + *dest = *src; + if constexpr (destroySourceInPlace) + { + src->T::~T(); + } + ++src; + ++dest; + } + } + } + + template + constexpr void MoveItems(T* dest, sizet count, T* source) + { + if (!std::is_constant_evaluated() && IsTriviallyMoveAssignable) + { + MoveMem(dest, source, count * sizeof(T)); + } + else + { + const T* const end = source + count; + while (source < end) + { + *dest = p::Forward(*source); + if constexpr (destroySourceInPlace) + { + source->T::~T(); + } + ++source; + ++dest; + } + } + } + + // Move items [source, source + count) backwards to [..., dest) + template + void MoveItemsBackwards(T* dest, sizet count, T* source) + { + if (!std::is_constant_evaluated() && IsTriviallyMoveAssignable) + { + // MoveMem (or std::memmove) is safe in overlapping memory + p::MoveMem(dest, source, count * sizeof(T)); + } + else + { + T* lastDest = dest + count; + T* lastSource = source + count; + while (lastSource != source) + { + *--lastDest = p::Forward(*--lastSource); + } + } + } + + template + constexpr void DestroyItems(T* data, sizet count) + { + if (!std::is_constant_evaluated() && IsTriviallyDestructible) + { + // Do nothing. No destruction needed. + } + else + { + const T* const end = data + count; + while (data < end) + { + data->T::~T(); + ++data; + } + } + } +#pragma endregion Memory Ops + +#pragma region Allocation + class Arena; + class HeapArena; + + void InitializeMemory(); + + // Native allocation functions + P_API void* HeapAlloc(sizet size); + P_API void* HeapAlloc(sizet size, sizet align); + P_API void* HeapRealloc(void* ptr, sizet size); + P_API void HeapFree(void* ptr); + + + P_API HeapArena& GetHeapArena(); + P_API Arena& GetCurrentArena(); + P_API void SetCurrentArena(Arena& arena); + + // Arena allocation functions (Find current arena) + P_API void* Alloc(sizet size); + P_API void* Alloc(sizet size, sizet align); + P_API bool Realloc(void* ptr, sizet ptrSize, sizet size); + P_API void Free(void* ptr, sizet size); + + + // Templated arena allocation functions: + + template ArenaT> + T* Alloc(ArenaT& arena, sizet count = 1) requires(!IsVoid) + { + return static_cast(arena.Alloc(sizeof(T) * count, alignof(T))); + } + template ArenaT> + T* Alloc(ArenaT& arena, sizet count, sizet align) requires(!IsVoid) + { + return static_cast(arena.Alloc(sizeof(T) * count, align)); + } + template ArenaT> + bool Realloc(ArenaT& arena, T* ptr, sizet ptrCount, sizet newCount) requires(!IsVoid) + { + return arena.Realloc(ptr, sizeof(T) * ptrCount, sizeof(T) * newCount); + } + template ArenaT> + void Free(ArenaT& arena, T* ptr, u32 count = 1) requires(!IsVoid) + { + arena.Free(static_cast(ptr), sizeof(T) * count); + } +#pragma endregion Allocation + +#pragma region Arena + struct P_API ArenaBlock + { + void* data = nullptr; + sizet size = 0; + + + ArenaBlock() = default; + ArenaBlock(void* data, sizet size) : data{data}, size{size} {} + ArenaBlock(ArenaBlock&& other) noexcept; + ArenaBlock& operator=(ArenaBlock&& other) noexcept; + ArenaBlock(const ArenaBlock& other) = default; + ArenaBlock& operator=(const ArenaBlock& other) = default; + + + bool IsAllocated() const + { + return data != nullptr; + } + + void* Begin() const + { + return data; + } + void* End() const + { + return static_cast(data) + size; + } + + bool Contains(void* ptr) const + { + return data <= ptr && static_cast(data) + size > ptr; + } + + const void* operator*() const + { + return data; + } + + void* operator*() + { + return data; + } + }; + + + /** Arena defines the API used on all other arena types */ + class P_API Arena + { + public: + using AllocSignature = void*(Arena*, sizet size); + using AllocAlignedSignature = void*(Arena*, sizet size, sizet align); + using ResizeSignature = bool(Arena*, void* ptr, sizet ptrSize, sizet size); + using FreeSignature = void(Arena*, void* ptr, sizet size); + + private: + AllocSignature* doAlloc = nullptr; + AllocAlignedSignature* doAllocAligned = nullptr; + ResizeSignature* doRealloc = nullptr; + FreeSignature* doFree = nullptr; + + + template + static consteval bool ImplementsAlloc() + { +#if defined(__GNUC__) // GCC won't detect this check as constexpr! :( + return true; +#else + return (void* (T::*)(sizet size))(&T::Alloc) + != (void* (T::*)(sizet size))(&Arena::Alloc); +#endif + } + template + static consteval bool ImplementsAllocAligned() + { +#if defined(__GNUC__) // GCC won't detect this check as constexpr! :( + return true; +#else + return (void* (T::*)(sizet size, sizet align))(&T::Alloc) + != (void* (T::*)(sizet size, sizet align))(&Arena::Alloc); +#endif + } + template + static consteval bool ImplementsRealloc() + { +#if defined(__GNUC__) // GCC won't detect this check as constexpr! :( + return true; +#else + return &T::Realloc != &Arena::Realloc; +#endif + } + template + static consteval bool ImplementsFree() + { +#if defined(__GNUC__) // GCC won't detect this check as constexpr! :( + return true; +#else + return &T::Free != &Arena::Free; +#endif + } + + protected: + + template T> + void Interface() + { + doAlloc = [](Arena* self, sizet size) { + static_assert(ImplementsAlloc() && "Alloc is not implemented"); + return static_cast(self)->Alloc(size); + }; + doAllocAligned = [](Arena* self, sizet size, sizet align) { + static_assert(ImplementsAllocAligned() && "Alloc (aligned) is not implemented"); + return static_cast(self)->Alloc(size, align); + }; + doRealloc = [](Arena* self, void* ptr, sizet ptrSize, sizet size) { + static_assert(ImplementsRealloc() && "Realloc is not implemented"); + return static_cast(self)->Realloc(ptr, ptrSize, size); + }; + doFree = [](Arena* self, void* ptr, sizet size) { + static_assert(ImplementsFree() && "Free is not implemented"); + return static_cast(self)->Free(ptr, size); + }; + } + + public: + Arena() = default; + virtual ~Arena() {} + Arena(const Arena&) = delete; + Arena& operator=(const Arena&) = delete; + + Arena(Arena&&) = default; + Arena& operator=(Arena&&) = default; + + + void* Alloc(sizet size) + { + return doAlloc(this, size); + } + void* Alloc(sizet size, sizet align) + { + return doAllocAligned(this, size, align); + } + bool Realloc(void* ptr, sizet ptrSize, sizet size) + { + return doRealloc(this, ptr, ptrSize, size); + } + void Free(void* ptr, sizet size) + { + doFree(this, ptr, size); + } + + virtual sizet GetAvailableMemory() const + { + return 0; + } + virtual void GetBlocks(TInlineArray& /**outBlocks*/) const {} + + virtual const struct MemoryStats* GetStats() const + { + return nullptr; + } + }; + + class P_API ChildArena : public Arena + { + protected: + Arena* parent = nullptr; + + public: + ChildArena(Arena* parent); + + Arena& GetParentArena() const + { + return *parent; + } + }; +#pragma endregion Arena + +#pragma region STL Allocator + template + struct STLAllocator + { + using value_type = T; + using size_type = sizet; + using difference_type = std::ptrdiff_t; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + + template + struct rebind + { + using other = STLAllocator; + }; + + Arena* arena = nullptr; + + + STLAllocator(Arena& arena = GetCurrentArena()) noexcept : arena{&arena} {} + STLAllocator(const STLAllocator& other) noexcept : arena{other.arena} {} + template + STLAllocator(const STLAllocator& other) noexcept : arena{other.arena} + {} + STLAllocator select_on_container_copy_construction() const + { + return *this; + } + +#if (__cplusplus >= 201703L) // C++17 + P_NODISCARD T* allocate(size_type count) + { + return p::Alloc(*arena, count); + } + P_NODISCARD T* allocate(size_type count, const void*) + { + return allocate(count); + } +#else + P_NODISCARD pointer allocate(size_type count, const void* = 0) + { + return p::Alloc(*arena, count); + } +#endif + constexpr void deallocate(T* p, size_type n) + { + p::Free(*arena, p, n); + } + + +#if ((__cplusplus >= 201103L) || (_MSC_VER > 1900)) // C++11 + using is_always_equal = std::true_type; + using propagate_on_container_copy_assignment = std::true_type; + using propagate_on_container_move_assignment = std::true_type; + using propagate_on_container_swap = std::true_type; + template + void construct(U* p, Args&&... args) + { + ::new (p) U(p::Forward(args)...); + } + template + void destroy(U* p) noexcept + { + p->~U(); + } +#else + void construct(pointer p, value_type const& val) + { + ::new (p) value_type(val); + } + void destroy(pointer p) + { + p->~value_type(); + } +#endif + + size_type max_size() const noexcept + { + return (Limits::Max() / sizeof(value_type)); + } + pointer address(reference x) const + { + return &x; + } + const_pointer address(const_reference x) const + { + return &x; + } + }; + + template + bool operator==(const STLAllocator& a, const STLAllocator& b) noexcept + { + return &a.arena == &b.arena; + } + template + bool operator!=(const STLAllocator& a, const STLAllocator& b) noexcept + { + return &a.arena != &b.arena; + } +#pragma endregion STL Allocator + +} // namespace p diff --git a/Include/PipeMemoryArenas.h b/Include/PipeMemoryArenas.h new file mode 100644 index 00000000..f300d5e7 --- /dev/null +++ b/Include/PipeMemoryArenas.h @@ -0,0 +1,419 @@ +// Copyright 2015-2024 Piperift - All rights reserved + +#pragma once + +#include "Pipe/Memory/MemoryStats.h" +#include "PipeMemory.h" + + +namespace p +{ +#pragma region Dummy Arena + /** This is an Arena example. + * It serves the single purpose of documenting an Arena's interface. + * Not intended to be used. + */ + class P_API DummyArena : public Arena + { + public: + DummyArena() + { + Interface(); + } + ~DummyArena() {} + + void* Alloc(const sizet size) + { + return nullptr; + } + void* Alloc(const sizet size, const sizet alignment) + { + return nullptr; + } + bool Realloc(void* ptr, const sizet ptrSize, const sizet size) + { + return false; + } + void Free(void* ptr, sizet size) {} + }; +#pragma endregion Dummy Arena + +#pragma region Heap Arena + class P_API HeapArena : public Arena + { + private: + MemoryStats stats; + + public: + HeapArena(); + + void* Alloc(const sizet size); + void* Alloc(const sizet size, const sizet align); + bool Realloc(void* ptr, const sizet ptrSize, const sizet size); + void Free(void* ptr, sizet size); + + const MemoryStats* GetStats() const override + { + return &stats; + } + }; +#pragma endregion Heap Arena + +#pragma region Mono Linear + /** + * LinearArena holds memory linearly in a block of memory. + * (Sometimes called ZoneArena if it resizes) + * Individual allocations can't be freed. It can + * be resized, but never smaller than its used size. + */ + class P_API MonoLinearArena : public ChildArena + { + private: + MemoryStats stats; + + protected: + void* insert = nullptr; + sizet count = 0; + ArenaBlock block{}; + bool selfAllocated = false; + + + public: + MonoLinearArena(ArenaBlock externalBlock, Arena& parentArena = GetCurrentArena()); + MonoLinearArena(const sizet blockSize = Memory::MB, Arena& parentArena = GetCurrentArena()); + ~MonoLinearArena() + { + Release(false); + } + + void* Alloc(sizet size); + + void* Alloc(sizet size, sizet align); + + bool Realloc(void* ptr, sizet ptrSize, sizet size) + { + return false; + } + void Free(void* ptr, sizet size); + + void Release(bool keepIfSelfAllocated = true); + + sizet GetAvailableMemory() const override + { + return block.size; + } + void GetBlocks(TArray& outBlocks) const override + { + outBlocks.Add(block); + } + + const MemoryStats* GetStats() const override + { + return &stats; + } + }; + + // TMonoLinearArena works like a MonoLinearArena but providing a block on the stack as the block + // to use + template + class P_API TMonoLinearArena : public MonoLinearArena + { + u8 buffer[blockSize]; + + + TMonoLinearArena(Arena& parentArena = GetCurrentArena()) + : MonoLinearArena(ArenaBlock{buffer, blockSize}, parentArena) + {} + }; +#pragma endregion Mono Linear + +#pragma region Multi Linear + namespace Details + { + struct P_API LinearBlock + { + sizet count = 0; // Counts number of live allocations + LinearBlock* next = nullptr; + LinearBlock* last = nullptr; + void* unaligned = nullptr; // Pointer where this block was allocated before alignment + + constexpr u8* End(sizet blockSize) const + { + return (u8*)unaligned + blockSize + sizeof(LinearBlock); + } + }; + + template + struct P_API LinearBasePool + { + void* insert = nullptr; // Pointer where to insert new allocations + LinearBlock* freeBlock = nullptr; + + + private: + void AllocateBlock(Arena& parentArena); + void FreeBlock(Arena& parentArena, LinearBlock* block); + + public: + void* Alloc(Arena& parentArena, sizet size, sizet align); + void Free(Arena& parentArena, void* ptr, sizet size); + + void Release(Arena& parentArena); + + static constexpr sizet GetBlockSize() + { + return blockSize; + } + + static constexpr sizet GetAllocatedBlockSize() + { + return blockSize + sizeof(LinearBlock); + } + + void* GetBlockEnd(LinearBlock* block) const + { + return (u8*)block->unaligned + GetAllocatedBlockSize(); + } + }; + + struct P_API LinearSmallPool : public LinearBasePool<1 * Memory::MB> + { + static constexpr sizet minSize = 0; + static constexpr sizet maxSize = 8 * Memory::KB; + }; + + struct P_API LinearMediumPool : public LinearBasePool<4 * Memory::MB> + { + static constexpr sizet minSize = LinearSmallPool::maxSize; + static constexpr sizet maxSize = 512 * Memory::KB; + }; + + struct P_API LinearBigPool : public LinearBasePool<16 * Memory::MB> + { + static constexpr sizet minSize = LinearMediumPool::maxSize; + static constexpr sizet maxSize = 4 * Memory::MB; + // Block size is the size of the allocation + }; + } // namespace Details + + /** + * LinearArena holds memory linearly in a block of memory. + * (Sometimes called ZoneArena if it resizes) + * Individual allocations can't be freed. It can + * be resized, but never smaller than its used size. + */ + struct P_API MultiLinearArena : public ChildArena + { + protected: + Details::LinearSmallPool smallPool; + Details::LinearMediumPool mediumPool; + Details::LinearBigPool bigPool; + + + public: + MultiLinearArena(Arena& parentArena = GetCurrentArena()); + ~MultiLinearArena() + { + Release(); + } + + void* Alloc(sizet size); + + void* Alloc(sizet size, sizet align); + + bool Realloc(void* ptr, sizet ptrSize, sizet size) + { + return false; + } + void Free(void* ptr, sizet size); + + void Release(); + + void Grow(sizet size, sizet align = 0); + }; +#pragma endregion Multi Linear + +#pragma region Best Fit Arena + class P_API BestFitArena : public ChildArena + { + public: + struct Slot + { + u8* start; + sizet size; + + u8* End() const + { + return start + size; + } + + auto operator==(const Slot& other) const + { + return size == other.size; + } + auto operator<(const Slot& other) const + { + return size < other.size; + } + auto operator>(const Slot& other) const + { + return size > other.size; + } + auto operator<=(const Slot& other) const + { + return size <= other.size; + } + auto operator>=(const Slot& other) const + { + return size >= other.size; + } + }; + + protected: + // TODO: Support growing multiple blocks + ArenaBlock block{}; + TArray freeSlots{}; + bool pendingSort = false; + sizet freeSize = 0; + + + public: + BestFitArena(Arena* parent, const sizet initialSize = 4 * Memory::KB); + BestFitArena(const sizet initialSize = 4 * Memory::KB) : BestFitArena(nullptr, initialSize) + {} + ~BestFitArena() override; + + void* Alloc(const sizet size); + void* Alloc(const sizet size, sizet align); + bool Realloc(void* ptr, const sizet ptrSize, const sizet size) + { + return false; + } + void Free(void* ptr, sizet size); + + const ArenaBlock& GetBlock() const + { + return block; + } + + bool Contains(void* ptr) + { + return block.Contains(ptr); + } + sizet GetFreeSize() + { + return freeSize; + } + + sizet GetUsedSize() + { + return block.size - freeSize; + } + + const TArray& GetFreeSlots() const + { + return freeSlots; + } + + private: + i32 FindSmallestSlot(sizet neededSize); + void ReduceSlot( + i32 slotIndex, Slot& slot, u8* const allocationStart, u8* const allocationEnd); + void AbsorbFreeSpace(u8* const allocationStart, u8* const allocationEnd); + }; +#pragma endregion Best Fit Arena + +#pragma region Big Best Fit Arena + class P_API BigBestFitArena : public ChildArena + { + public: + struct AllocationHeader + { + u8* end; + }; + + struct P_API Slot + { + u32 offset; + u32 size; + + bool operator==(const Slot& other) const + { + return size == other.size; + } + auto operator<=>(const Slot& other) const + { + return size <=> other.size; + } + }; + + protected: + static constexpr sizet minAlignment = sizeof(AllocationHeader); + // TODO: Support growing multiple blocks + ArenaBlock block{}; + TArray freeSlots{}; + bool pendingSort = false; + sizet freeSize = 0; + + + public: + BigBestFitArena(Arena* parent, const sizet initialSize = 1024); + BigBestFitArena(const sizet initialSize = 1024) : BigBestFitArena(nullptr, initialSize) {} + ~BigBestFitArena() override; + + void* Alloc(const sizet size); + void* Alloc(const sizet size, sizet alignment); + bool Realloc(void* ptr, sizet ptrSize, sizet size) + { + return false; + } + void Free(void* ptr, sizet size); + + const ArenaBlock& GetBlock() const + { + return block; + } + + bool Contains(void* ptr) + { + return block.Contains(ptr); + } + sizet GetFreeSize() + { + return freeSize; + } + + sizet GetUsedSize() + { + return block.size - freeSize; + } + + const TArray& GetFreeSlots() const + { + return freeSlots; + } + + void* GetAllocationStart(void* ptr) const + { + return GetHeader(ptr); + } + void* GetAllocationEnd(void* ptr) const + { + return GetHeader(ptr)->end; + } + + private: + AllocationHeader* GetHeader(void* ptr) const + { + return reinterpret_cast( + static_cast(ptr) - sizeof(AllocationHeader)); + } + + i32 FindSmallestSlot(sizet size); + void ReduceSlot(i32 slotIndex, Slot& slot, u32 allocationStart, u32 allocationEnd); + void AbsorbFreeSpace(u32 allocationStart, u32 allocationEnd); + + static u32 ToOffset(void* data, void* block); + }; +#pragma endregion Big Best Fit Arena +} // namespace p diff --git a/Include/Pipe/Memory/NewDelete.h b/Include/PipeNewDelete.h similarity index 90% rename from Include/Pipe/Memory/NewDelete.h rename to Include/PipeNewDelete.h index 16d251ea..c9d9cc8e 100644 --- a/Include/Pipe/Memory/NewDelete.h +++ b/Include/PipeNewDelete.h @@ -9,8 +9,8 @@ #if defined(__cplusplus) - #include "Pipe/Core/Platform.h" - #include "Pipe/Memory/Alloc.h" + #include "PipePlatform.h" + #include "PipeMemory.h" #include @@ -58,14 +58,4 @@ void operator delete (void* p, std::align_val_t, const std::nothrow_t&) noexcep void operator delete[](void* p, std::align_val_t, const std::nothrow_t&) noexcept { p::HeapFree(p); } # endif // clang-format on - - #define P_DO_OVERRIDE_NEW_DELETE - - - // If Pipe is linked statically, only Pipe overrides new/delete - #ifdef PIPE_STATIC_DEFINE - #define P_OVERRIDE_NEW_DELETE - #else - #define P_OVERRIDE_NEW_DELETE P_DO_OVERRIDE_NEW_DELETE - #endif #endif diff --git a/Include/PipePlatform.h b/Include/PipePlatform.h new file mode 100644 index 00000000..25337082 --- /dev/null +++ b/Include/PipePlatform.h @@ -0,0 +1,367 @@ +// Copyright 2015-2024 Piperift - All rights reserved + +#pragma once + +#include "Pipe/Export.h" + +#include +#include + +// Platform Break includes +#ifdef _MSC_VER +#elif defined(__i386__) || defined(__x86_64__) +#elif defined(__thumb__) +#elif defined(__arm__) && !defined(__thumb__) +#elif defined(__aarch64__) && defined(__clang__) +#elif defined(__aarch64__) +#elif defined(__powerpc__) +#elif defined(__riscv) +#else + #include +#endif + + +#pragma region Platform Macros +#ifndef P_PLATFORM_WINDOWS + #if defined(_WIN64) || defined(_WIN32) + #define P_PLATFORM_WINDOWS 1 + #else + #define P_PLATFORM_WINDOWS 0 + #endif +#endif +#ifndef P_PLATFORM_LINUX + #if defined(__linux__) + #define P_PLATFORM_LINUX 1 + #if defined(__ANDROID__) || defined(ANDROID) + #define P_PLATFORM_ANDROID 1 + #endif + #else + #define P_PLATFORM_LINUX 0 + #define P_PLATFORM_ANDROID 0 + #endif +#endif +#ifndef P_PLATFORM_APPLE + #if defined(__APPLE__) || defined(__APPLE_CC__) + #define P_PLATFORM_APPLE 1 + #if defined(__IPHONE__) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) \ + || (defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR) + #define P_PLATFORM_IOS 1 + #define P_PLATFORM_MACOS 0 + #else + #define P_PLATFORM_MACOS 1 + #define P_PLATFORM_IOS 0 + #endif + #else + #define P_PLATFORM_APPLE 0 + #define P_PLATFORM_MACOS 0 + #define P_PLATFORM_IOS 0 + #endif +#endif +#ifndef P_PLATFORM_BSD + #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \ + || defined(__DragonFly__) + #define P_PLATFORM_BSD 1 + #else + #define P_PLATFORM_BSD 0 + #endif +#endif + + +#if P_PLATFORM_WINDOWS + #define P_FORCEINLINE __forceinline /* Force code to be inline */ + #define P_NOINLINE __declspec(noinline) /* Force code to not be inlined */ +#elif P_PLATFORM_LINUX + #if P_DEBUG + #define P_FORCEINLINE inline + #else + #define P_FORCEINLINE inline __attribute__((always_inline)) + #endif // P_DEBUG + #define P_NOINLINE __attribute__((noinline)) +#elif P_PLATFORM_MACOS + #if P_DEBUG + #define P_FORCEINLINE inline /* Don't force code to be inline */ + #else + #define P_FORCEINLINE inline __attribute__((always_inline)) /* Force code to be inline */ + #endif + #define P_NOINLINE __attribute__((noinline)) +#else + #error Unknown platform +#endif + + +#ifndef P_LIKELY + #if (defined(__clang__) || defined(__GNUC__)) && (P_PLATFORM_LINUX) + #define P_LIKELY(x) __builtin_expect(!!(x), 1) + #else + #define P_LIKELY(x) (x) + #endif +#endif +#ifndef P_UNLIKELY + #if (defined(__clang__) || defined(__GNUC__)) && (P_PLATFORM_LINUX) + #define P_UNLIKELY(x) __builtin_expect(!!(x), 0) + #else + #define P_UNLIKELY(x) (x) + #endif +#endif + +#undef P_UNIQUE_FUNCTION_ID +#if defined(_MSC_VER) + #define P_UNIQUE_FUNCTION_ID __FUNCSIG__ +#else + #if defined(__GNUG__) + #define P_UNIQUE_FUNCTION_ID __PRETTY_FUNCTION__ + #endif +#endif + +#ifndef P_FORCEINLINE + #if defined(_MSC_VER) + #define P_FORCEINLINE __forceinline + #else + #define P_FORCEINLINE inline __attribute__((always_inline)) + #endif +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201703) + #define P_NODISCARD [[nodiscard]] +#elif (defined(__GNUC__) && (__GNUC__ >= 4)) \ + || defined(__clang__) // includes clang, icc, and clang-cl + #define P_NODISCARD __attribute__((warn_unused_result)) +#elif defined(_HAS_NODISCARD) + #define P_NODISCARD _NODISCARD +#elif (_MSC_VER >= 1700) + #define P_NODISCARD _Check_return_ +#else + #define P_NODISCARD +#endif + +#if defined(_MSC_VER) || defined(__MINGW32__) + #if defined(__MINGW32__) + #define P_RESTRICT + #define P_ATTR_MALLOC __attribute__((malloc)) + #else + #if (_MSC_VER >= 1900) && !defined(__EDG__) + #define P_RESTRICT __declspec(allocator) __declspec(restrict) + #else + #define P_RESTRICT __declspec(restrict) + #endif + #define P_ATTR_MALLOC + #endif +#elif defined(__GNUC__) // includes clang and icc + #define P_RESTRICT + #define P_ATTR_MALLOC __attribute__((malloc)) +#else + #define P_RESTRICT + #define P_ATTR_MALLOC +#endif +#pragma endregion Macros + + +#pragma region Platform Types +namespace p +{ + //--------------------------------------------------------------------- + // Utility for automatically setting up the pointer-sized integer type + //--------------------------------------------------------------------- + + template + struct SelectIntPointerType + { + // nothing here are is it an error if the partial specializations fail + }; + + template + struct SelectIntPointerType + { + typedef T64BITS TIntPointer; // select the 64 bit type + }; + + template + struct SelectIntPointerType + { + using TIntPointer = T32BITS; // select the 32 bit type + }; + + + /** + * Generic types for almost all compilers and platforms + */ + struct P_API GenericPlatformTypes + { + // Character types. + // An ANSI character - 8-bit fixed-width representation of 7-bit characters. + using AnsiChar = char; + + // A wide character - In-memory only. ?-bit fixed-width representation of + // the platform's natural wide character set. Could be different sizes on + // different platforms. + using WideChar = wchar_t; + + // An 8-bit character type - In-memory only. 8-bit representation. Should + // really be char8_t but making this the generic option is easier for + // compilers which don't fully support C++11 yet (i.e. MSVC). + using Char8 = unsigned char; + + // A 16-bit character type - In-memory only. 16-bit representation. Should + // really be char16_t but making this the generic option is easier for + // compilers which don't fully support C++11 yet (i.e. MSVC). + using Char16 = unsigned short int; + + // A 32-bit character type - In-memory only. 32-bit representation. Should + // really be char32_t but making this the generic option is easier for + // compilers which don't fully support C++11 yet (i.e. MSVC). + using Char32 = unsigned int; + + // unsigned int the same size as a pointer + using uPtr = SelectIntPointerType::TIntPointer; + + // signed int the same size as a pointer + using iPtr = SelectIntPointerType::TIntPointer; + + // unsigned int the same size as a pointer + using sizet = uPtr; + + // signed int the same size as a pointer + using ssizet = iPtr; + + using TYPE_OF_NULL = std::int32_t; + using TYPE_OF_NULLPTR = decltype(nullptr); + }; + + + struct P_API PlatformTypes : public GenericPlatformTypes + { +#if P_PLATFORM_WINDOWS + using sizet = std::size_t; +#elif P_PLATFORM_LINUX + using DWORD = unsigned int; + using sizet = __SIZE_TYPE__; + using TYPE_OF_NULL = decltype(__null); +#elif P_PLATFORM_MACOS + using DWORD = unsigned int; + using sizet = std::size_t; + using TYPE_OF_NULL = decltype(nullptr); + using Char16 = char16_t; +#endif + }; + + + ///////////////////////////////////////////////////////////// + // Define platform types as global types + + // Unsigned base types + // An 8-bit unsigned integer + using u8 = std::uint8_t; + // A 16-bit unsigned integer + using u16 = std::uint16_t; + // A 32-bit unsigned integer + using u32 = std::uint32_t; + // A 64-bit unsigned integer + using u64 = std::uint64_t; + + // Signed base types + // An 8-bit signed integer + using i8 = std::int8_t; + /// A 16-bit signed integer + using i16 = std::int16_t; + /// A 32-bit signed integer + using i32 = std::int32_t; + /// A 64-bit signed integer + using i64 = std::int64_t; + /// A 32-bit floating point number + using f32 = float; + /// A 64-bit floating point number + using f64 = double; + + // Character types + // An ANSI character. Normally a signed type. + using AnsiChar = PlatformTypes::AnsiChar; + // A wide character. Normally a signed type. + using WideChar = PlatformTypes::WideChar; + // An 8-bit character containing a UTF8 (Unicode, 8-bit, variable-width) code unit. + using Char8 = PlatformTypes::Char8; + // A 16-bit character containing a UTF16 (Unicode, 16-bit, variable-width) code unit. + using Char16 = PlatformTypes::Char16; + // A 32-bit character containing a UTF32 (Unicode, 32-bit, fixed-width) code unit. + using Char32 = PlatformTypes::Char32; + + // Pointer types + // An unsigned integer the same size as a pointer + using uPtr = PlatformTypes::uPtr; + // A signed integer the same size as a pointer + using iPtr = PlatformTypes::iPtr; + // An unsigned integer the same size as a pointer, the same as UPTRINT + using sizet = PlatformTypes::sizet; + // An integer the same size as a pointer, the same as PTRINT + using ssizet = PlatformTypes::ssizet; + + // The type of the NULL constant. + using TYPE_OF_NULL = PlatformTypes::TYPE_OF_NULL; + // The type of the C++ nullptr keyword. + using TYPE_OF_NULLPTR = PlatformTypes::TYPE_OF_NULLPTR; + + constexpr i32 NO_INDEX = -1; +} // namespace p +#pragma endregion Platform Types + + +#pragma region Platform Break +namespace p +{ + P_FORCEINLINE static void PlatformBreak() + { +#ifdef _MSC_VER // MSVC + __debugbreak(); +#elif defined(__i386__) || defined(__x86_64__) + __asm__ volatile("int $0x03"); +#elif defined(__thumb__) + // See 'arm-linux-tdep.c' in GDB source, 'eabi_linux_thumb_le_breakpoint' + __asm__ volatile(".inst 0xde01"); +#elif defined(__arm__) && !defined(__thumb__) + // See 'arm-linux-tdep.c' in GDB source, 'eabi_linux_arm_le_breakpoint' + __asm__ volatile(".inst 0xe7f001f0"); +#elif defined(__aarch64__) && defined(__clang__) + __builtin_debugtrap(); +#elif defined(__aarch64__) + // See 'aarch64-tdep.c' in GDB source, 'aarch64_default_breakpoint' + __asm__ volatile(".inst 0xd4200000"); +#elif defined(__powerpc__) + // See 'rs6000-tdep.c' in GDB source, 'rs6000_breakpoint' + __asm__ volatile(".4byte 0x7d821008"); +#elif defined(__riscv) + // See 'riscv-tdep.c' in GDB source, 'riscv_sw_breakpoint_from_kind' + __asm__ volatile(".4byte 0x00100073"); +#else + raise(SIGTRAP); +#endif + } + +#if P_DEBUG + P_FORCEINLINE static void PlatformDebugBreak() + { + PlatformBreak(); + } +#else + P_FORCEINLINE static void PlatformDebugBreak() {} +#endif +} // namespace p + +#pragma endregion Platform Break + +#pragma region Platform Misc +namespace p +{ + struct Guid; + + struct P_API PlatformMisc + { + static void CreateGuid(struct Guid& guid); + + static u64 GetCycles64(); + + static u32 GetCycles() + { + return u32(GetCycles64()); + } + }; +} // namespace p +#pragma endregion Platform Misc \ No newline at end of file diff --git a/Include/PipeReflect.h b/Include/PipeReflect.h index 0ae5b46f..14dd16ba 100644 --- a/Include/PipeReflect.h +++ b/Include/PipeReflect.h @@ -132,8 +132,8 @@ namespace p return requires(const T v, p::Writer writer) { v.WriteProperties(writer); }; } - PIPE_API void Read(Reader& r, TypeId& val); - PIPE_API void Write(Writer& w, TypeId val); + P_API void Read(Reader& r, TypeId& val); + P_API void Write(Writer& w, TypeId val); template void Read(Reader& ct, T& value) requires( @@ -234,7 +234,7 @@ namespace p #pragma region Runtime - struct PIPE_API TypeProperty + struct P_API TypeProperty { using AccessFunc = void*(void*); @@ -249,7 +249,7 @@ namespace p }; - struct PIPE_API TypeOps + struct P_API TypeOps { using ReadFunc = void(Reader&, void*); using WriteFunc = void(Writer&, void*); @@ -258,7 +258,7 @@ namespace p WriteFunc* write = nullptr; }; - struct PIPE_API EnumTypeOps : public TypeOps + struct P_API EnumTypeOps : public TypeOps { u32 valueSize = 0; TArray values; @@ -285,14 +285,14 @@ namespace p i32 Size() const; }; - struct PIPE_API ObjectTypeOps : public TypeOps + struct P_API ObjectTypeOps : public TypeOps { using NewFunc = BaseObject*(Arena& arena); NewFunc* onNew = nullptr; }; - struct PIPE_API ContainerTypeOps : public TypeOps + struct P_API ContainerTypeOps : public TypeOps { using GetDataFunc = void*(void*); using GetSizeFunc = i32(void*); @@ -321,30 +321,30 @@ namespace p struct TypeRegistry; namespace details { - PIPE_API i32 GetTypeIndex(const TypeRegistry& registry, TypeId id); - PIPE_API bool HasTypeFlags(const TypeRegistry& registry, i32 index, TypeFlags flags); - PIPE_API bool HasAnyTypeFlags(const TypeRegistry& registry, i32 index, TypeFlags flags); + P_API i32 GetTypeIndex(const TypeRegistry& registry, TypeId id); + P_API bool HasTypeFlags(const TypeRegistry& registry, i32 index, TypeFlags flags); + P_API bool HasAnyTypeFlags(const TypeRegistry& registry, i32 index, TypeFlags flags); } // namespace details - PIPE_API bool InitializeReflect(); - PIPE_API void OnReflectInit(void (*callback)()); - - PIPE_API TView GetRegisteredTypeIds(); - PIPE_API bool IsTypeRegistered(TypeId id); - PIPE_API TypeId GetTypeParent(TypeId id); - PIPE_API bool IsTypeParentOf(TypeId parentId, TypeId childId); - PIPE_API sizet GetTypeSize(TypeId id); - PIPE_API StringView GetTypeName(TypeId id); - PIPE_API TypeFlags GetTypeFlags(TypeId id); - PIPE_API bool HasTypeFlags(TypeId id, TypeFlags flags); - PIPE_API bool HasAnyTypeFlags(TypeId id, TypeFlags flags); + P_API bool InitializeReflect(); + P_API void OnReflectInit(void (*callback)()); + + P_API TView GetRegisteredTypeIds(); + P_API bool IsTypeRegistered(TypeId id); + P_API TypeId GetTypeParent(TypeId id); + P_API bool IsTypeParentOf(TypeId parentId, TypeId childId); + P_API sizet GetTypeSize(TypeId id); + P_API StringView GetTypeName(TypeId id); + P_API TypeFlags GetTypeFlags(TypeId id); + P_API bool HasTypeFlags(TypeId id, TypeFlags flags); + P_API bool HasAnyTypeFlags(TypeId id, TypeFlags flags); /// @return the properties of this type, excluding those of the parent (if any) - PIPE_API TView GetOwnTypeProperties(TypeId id); + P_API TView GetOwnTypeProperties(TypeId id); /// @return the properties of this type - PIPE_API TView GetTypeProperties(TypeId id); - PIPE_API const TypeOps* GetTypeOps(TypeId id); - PIPE_API const ObjectTypeOps* GetTypeObjectOps(TypeId id); - PIPE_API const ContainerTypeOps* GetTypeContainerOps(TypeId id); + P_API TView GetTypeProperties(TypeId id); + P_API const TypeOps* GetTypeOps(TypeId id); + P_API const ObjectTypeOps* GetTypeObjectOps(TypeId id); + P_API const ContainerTypeOps* GetTypeContainerOps(TypeId id); #pragma region Registration @@ -390,15 +390,15 @@ namespace p template TypeId RegisterTypeId(); // Forward declaration - PIPE_API bool BeginTypeId(TypeId id); - PIPE_API void EndTypeId(); - PIPE_API void SetTypeParent(TypeId parentId); - PIPE_API void SetTypeSize(sizet size); - PIPE_API void SetTypeName(StringView name); - PIPE_API void SetTypeFlags(TypeFlags flags); - PIPE_API void AddTypeFlags(TypeFlags flags); - PIPE_API void RemoveTypeFlags(TypeFlags flags); - PIPE_API void AddTypeProperty(const TypeProperty& property); + P_API bool BeginTypeId(TypeId id); + P_API void EndTypeId(); + P_API void SetTypeParent(TypeId parentId); + P_API void SetTypeSize(sizet size); + P_API void SetTypeName(StringView name); + P_API void SetTypeFlags(TypeFlags flags); + P_API void AddTypeFlags(TypeFlags flags); + P_API void RemoveTypeFlags(TypeFlags flags); + P_API void AddTypeProperty(const TypeProperty& property); template constexpr void AddTypeProperty( TypeProperty::AccessFunc* access, StringView name, PropertyFlags flags = PF_None) @@ -416,7 +416,7 @@ namespace p .typeId = RegisterTypeId(), .flags = flags, .access = access, .name = name}); } - PIPE_API void SetTypeOps(const TypeOps* operations); + P_API void SetTypeOps(const TypeOps* operations); template void AssignSerializableTypeOps(TypeOps& ops) { @@ -470,9 +470,10 @@ namespace p } - template + template TypeId RegisterTypeId() { // Static to only register once + using T = Mut; static bool bRegistered = false; const TypeId typeId = GetTypeId(); if (!bRegistered && BeginTypeId(typeId)) @@ -552,22 +553,26 @@ namespace p template - void CallSuperReadProperties(T& value, p::Reader& r) + void CallSuperReadProperties(T& /**value*/, p::Reader& /**r*/) {} template void CallSuperReadProperties(const T& value, p::Reader& r) requires(p::HasSuper()) { if constexpr (p::HasReadProperties()) + { value.Super::ReadProperties(r); + } } template - void CallSuperWriteProperties(const T& value, p::Writer& w) + void CallSuperWriteProperties(const T& /**value*/, p::Writer& /**w*/) {} template void CallSuperWriteProperties(const T& value, p::Writer& w) requires(p::HasSuper()) { if constexpr (p::HasWriteProperties()) + { value.Super::WriteProperties(w); + } } } // namespace p @@ -608,7 +613,7 @@ public: \ using Self = type; \ static constexpr p::TypeFlags staticTypeFlags = p::InitTypeFlags(flags); \ \ - p::TypeId GetTypeId() const override \ + p::TypeId ProvideTypeId() const override \ { \ return p::GetTypeId(); \ } @@ -672,11 +677,9 @@ private: \ \ static void __BuildProperty(p::MetaCounter) \ { \ - p::AddTypeProperty( \ - [](void* instance) { \ + p::AddTypeProperty([](void* instance) { \ return (void*)&static_cast(instance)->name; \ - }, \ - #name, p::InitPropertyFlags(flags)); \ + }, #name, p::InitPropertyFlags(flags)); \ /* Registry next property if any */ \ __BuildProperty(p::MetaCounter{}); \ } \ @@ -739,8 +742,8 @@ P_NATIVE_NAMED(p::u64, "u64") P_NATIVE_NAMED(p::i64, "i64") P_NATIVE(float) P_NATIVE(double) -P_NATIVE_NAMED(p::TChar, "TChar") -P_NATIVE_NAMED(p::TChar*, "StringLiteral") +P_NATIVE(char) +P_NATIVE_NAMED(char*, "StringLiteral") P_NATIVE_NAMED(p::StringView, "StringView") P_NATIVE_NAMED(p::String, "String") P_NATIVE_NAMED(p::Path, "Path"); @@ -767,42 +770,42 @@ namespace p inline void BuildType(const T*) requires(p::IsArray()) { // clang-format off - static p::ContainerTypeOps ops{ - .itemType = p::RegisterTypeId(), - .getData = [](void* data) { - return (void*)static_cast(data)->Data(); - }, - .getSize = [](void* data) { - return static_cast(data)->Size(); - }, - .getItem = [](void* data, p::i32 index) { - return (void*)(static_cast(data)->Data() + index); - }, - .addItem = [](void* data, void* item) { - if (item) - { - auto& itemRef = *static_cast(item); - if constexpr (p::IsCopyAssignable) + static p::ContainerTypeOps ops{ + .itemType = p::RegisterTypeId(), + .getData = [](void* data) { + return (void*)static_cast(data)->Data(); + }, + .getSize = [](void* data) { + return static_cast(data)->Size(); + }, + .getItem = [](void* data, p::i32 index) { + return (void*)(static_cast(data)->Data() + index); + }, + .addItem = [](void* data, void* item) { + if (item) { - static_cast(data)->Add(itemRef); + auto& itemRef = *static_cast(item); + if constexpr (p::IsCopyAssignable) + { + static_cast(data)->Add(itemRef); + } + else + { + static_cast(data)->Add(p::Move(itemRef)); + } } else { - static_cast(data)->Add(p::Move(itemRef)); + static_cast(data)->Add(); } + }, + .removeItem = [](void* data, p::i32 index) { + static_cast(data)->RemoveAt(index); + }, + .clear = [](void* data) { + static_cast(data)->Clear(); } - else - { - static_cast(data)->Add(); - } - }, - .removeItem = [](void* data, p::i32 index) { - static_cast(data)->RemoveAt(index); - }, - .clear = [](void* data) { - static_cast(data)->Clear(); - } - }; + }; // clang-format on p::AddTypeFlags(p::TF_Container); p::SetTypeOps(&ops); @@ -814,7 +817,7 @@ namespace p namespace p { #pragma region Objects - class PIPE_API BaseObject + class P_API BaseObject : public Casteable { protected: BaseObject() = default; @@ -822,13 +825,12 @@ namespace p public: virtual ~BaseObject() = default; - TypeId GetTypeId() const; TPtr AsPtr() const; }; // For shared export purposes, we separate pointers from the exported Class - struct PIPE_API ObjectOwnership + struct P_API ObjectOwnership { TPtr self; TPtr owner; @@ -856,14 +858,14 @@ namespace p // Allow creation of classes using reflection static T* New(Arena& arena, TypeId type, TPtr owner = {}) { - if (auto* ops = GetTypeObjectOps(type)) + if (GetTypeId() == type || IsTypeParentOf(GetTypeId(), type)) { - // Sets owner during construction - // TODO: Fix self not existing at the moment of construction - ObjectOwnership::nextOwner = owner; - if (T* instance = dynamic_cast(ops->onNew(arena))) + if (auto* ops = GetTypeObjectOps(type)) { - return instance; + // Sets owner during construction + // TODO: Fix self not existing at the moment of construction + ObjectOwnership::nextOwner = owner; + return Cast(ops->onNew(arena)); } } return nullptr; @@ -879,14 +881,14 @@ namespace p }; - class PIPE_API Object : public BaseObject + class P_API Object : public BaseObject { public: using Self = Object; template using PtrBuilder = TObjectPtrBuilder; - virtual p::TypeId GetTypeId() const + p::TypeId ProvideTypeId() const override { return p::GetTypeId(); } @@ -906,13 +908,71 @@ namespace p template TPtr AsPtr() const { - return ownership.AsPtr().Cast(); + return Cast(ownership.AsPtr()); } template TPtr GetOwner() const { - return ownership.GetOwner().Cast(); + return Cast(ownership.GetOwner()); } }; #pragma endregion Objects + + +#pragma region Casts + + /** + * Cast a pointer into another type doing up-casting + */ + template + To* Cast(From* value) requires(Derived) + { + return value; + } + + /** + * Cast a pointer into another type doing down-casting + */ + template + To* Cast(From* value) requires(Derived, From, false>) + { + static_assert(!IsPointer, + "Destination type of a Cast can't be a pointer. Use Cast() instead of " + "Cast()."); + using ToValue = std::remove_pointer_t; + if (value) + { + const TypeId toId = GetTypeId(); + const TypeId fromId = value->GetTypeId(); + if (toId == fromId || IsTypeParentOf(toId, fromId)) + { + return static_cast(value); + } + } + return nullptr; + } + + template + TPtr Cast(const TPtr& value) + { + if (Cast(value.Get())) + { + TPtr ptr{}; + ptr.CopyFromUnsafe(value); + return ptr; + } + return {}; + } + + template + TPtr Cast(const TOwnPtr& value) + { + if constexpr (Derived) // Is T2 is T or its base + { + return TPtr{value}; + } + TPtr ptr{value}; + return Cast(ptr); + } +#pragma endregion Casts }; // namespace p diff --git a/Include/PipeSerialize.h b/Include/PipeSerialize.h index 4fd414c7..5a7bc3d6 100644 --- a/Include/PipeSerialize.h +++ b/Include/PipeSerialize.h @@ -1,14 +1,15 @@ // Copyright 2015-2024 Piperift - All rights reserved #pragma once -#include "Pipe/Core/Platform.h" #include "Pipe/Core/String.h" #include "Pipe/Core/StringView.h" #include "Pipe/Core/Templates.h" #include "Pipe/Core/TypeFlags.h" +#include "Pipe/Core/TypeId.h" #include "Pipe/Core/TypeTraits.h" #include "PipeArrays.h" #include "PipeColor.h" +#include "PipePlatform.h" #include "PipeSerializeFwd.h" #include @@ -40,7 +41,7 @@ namespace p #pragma region Reader - struct PIPE_API Reader + struct P_API Reader : public Casteable { friend IFormatReader; IFormatReader* formatReader = nullptr; @@ -146,21 +147,27 @@ namespace p { return *formatReader; } + + protected: + TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } }; // Format reads - PIPE_API void Read(Reader& r, bool& val); - PIPE_API void Read(Reader& r, i8& val); - PIPE_API void Read(Reader& r, u8& val); - PIPE_API void Read(Reader& r, i16& val); - PIPE_API void Read(Reader& r, u16& val); - PIPE_API void Read(Reader& r, i32& val); - PIPE_API void Read(Reader& r, u32& val); - PIPE_API void Read(Reader& r, i64& val); - PIPE_API void Read(Reader& r, u64& val); - PIPE_API void Read(Reader& r, float& val); - PIPE_API void Read(Reader& r, double& val); - PIPE_API void Read(Reader& r, StringView& val); + P_API void Read(Reader& r, bool& val); + P_API void Read(Reader& r, i8& val); + P_API void Read(Reader& r, u8& val); + P_API void Read(Reader& r, i16& val); + P_API void Read(Reader& r, u16& val); + P_API void Read(Reader& r, i32& val); + P_API void Read(Reader& r, u32& val); + P_API void Read(Reader& r, i64& val); + P_API void Read(Reader& r, u64& val); + P_API void Read(Reader& r, float& val); + P_API void Read(Reader& r, double& val); + P_API void Read(Reader& r, StringView& val); template void Read(Reader& r, TPair& val) @@ -192,7 +199,7 @@ namespace p #pragma region Writer - struct PIPE_API Writer + struct P_API Writer : public Casteable { friend IFormatWriter; IFormatWriter* formatWriter = nullptr; @@ -296,21 +303,27 @@ namespace p { return *formatWriter; } + + protected: + TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } }; // Format writes - PIPE_API void Write(Writer& w, bool val); - PIPE_API void Write(Writer& w, i8 val); - PIPE_API void Write(Writer& w, u8 val); - PIPE_API void Write(Writer& w, i16 val); - PIPE_API void Write(Writer& w, u16 val); - PIPE_API void Write(Writer& w, i32 val); - PIPE_API void Write(Writer& w, u32 val); - PIPE_API void Write(Writer& w, i64 val); - PIPE_API void Write(Writer& w, u64 val); - PIPE_API void Write(Writer& w, float val); - PIPE_API void Write(Writer& w, double val); - PIPE_API void Write(Writer& w, StringView val); + P_API void Write(Writer& w, bool val); + P_API void Write(Writer& w, i8 val); + P_API void Write(Writer& w, u8 val); + P_API void Write(Writer& w, i16 val); + P_API void Write(Writer& w, u16 val); + P_API void Write(Writer& w, i32 val); + P_API void Write(Writer& w, u32 val); + P_API void Write(Writer& w, i64 val); + P_API void Write(Writer& w, u64 val); + P_API void Write(Writer& w, float val); + P_API void Write(Writer& w, double val); + P_API void Write(Writer& w, StringView val); template void Write(Writer& w, TPair& val) @@ -341,7 +354,7 @@ namespace p #pragma region ReadWriter - struct PIPE_API ReadWriter + struct P_API ReadWriter { private: Reader* reader; @@ -459,7 +472,7 @@ namespace p // and Write template void Read(Reader& ct, T& val) - requires(bool(TFlags::HasSingleSerialize&& TFlags::HasMemberSerialize)) + requires(bool(TFlags::HasSingleSerialize && TFlags::HasMemberSerialize)) { ReadWriter commonContext{ct}; val.Serialize(commonContext); @@ -476,7 +489,7 @@ namespace p // and Write template void Write(Writer& ct, const T& val) - requires(bool(TFlags::HasSingleSerialize&& TFlags::HasMemberSerialize)) + requires(bool(TFlags::HasSingleSerialize && TFlags::HasMemberSerialize)) { ReadWriter commonContext{ct}; const_cast(val).Serialize(commonContext); @@ -492,7 +505,7 @@ namespace p #pragma region Format Interface - struct PIPE_API IFormatReader + struct P_API IFormatReader { private: Reader reader{}; @@ -541,7 +554,7 @@ namespace p }; - struct PIPE_API IFormatWriter + struct P_API IFormatWriter { private: Writer writer{}; @@ -663,43 +676,43 @@ namespace p * @param data containing the constant json string * @see JsonFormatReader(String& data, bool insitu = true) for optional insitu reading */ - PIPE_API explicit JsonFormatReader(StringView data); + P_API explicit JsonFormatReader(StringView data); /** * Configures a JsonFormatReader to read from an string buffer * This contructor might MODIFY the buffer if needed to improve reading speed * slightly. * @param data containing the MUTABLE json string */ - PIPE_API explicit JsonFormatReader(String& data); - PIPE_API ~JsonFormatReader(); - - PIPE_API void BeginObject() override; - PIPE_API void BeginArray(u32& size) override; - - PIPE_API bool EnterNext(StringView name) override; - PIPE_API bool EnterNext() override; - PIPE_API void Leave() override; - - PIPE_API void Read(bool& val) override; - PIPE_API void Read(i8& val) override; - PIPE_API void Read(u8& val) override; - PIPE_API void Read(i16& val) override; - PIPE_API void Read(u16& val) override; - PIPE_API void Read(i32& val) override; - PIPE_API void Read(u32& val) override; - PIPE_API void Read(i64& val) override; - PIPE_API void Read(u64& val) override; - PIPE_API void Read(float& val) override; - PIPE_API void Read(double& val) override; - PIPE_API void Read(StringView& val) override; - - PIPE_API bool IsObject() const override; - PIPE_API bool IsArray() const override; - PIPE_API bool IsValid() const override + P_API explicit JsonFormatReader(String& data); + P_API ~JsonFormatReader(); + + P_API void BeginObject() override; + P_API void BeginArray(u32& size) override; + + P_API bool EnterNext(StringView name) override; + P_API bool EnterNext() override; + P_API void Leave() override; + + P_API void Read(bool& val) override; + P_API void Read(i8& val) override; + P_API void Read(u8& val) override; + P_API void Read(i16& val) override; + P_API void Read(u16& val) override; + P_API void Read(i32& val) override; + P_API void Read(u32& val) override; + P_API void Read(i64& val) override; + P_API void Read(u64& val) override; + P_API void Read(float& val) override; + P_API void Read(double& val) override; + P_API void Read(StringView& val) override; + + P_API bool IsObject() const override; + P_API bool IsArray() const override; + P_API bool IsValid() const override { return root != nullptr; } - PIPE_API const ReadError& GetError() const + P_API const ReadError& GetError() const { return error; } @@ -732,39 +745,39 @@ namespace p public: - PIPE_API JsonFormatWriter(); - PIPE_API ~JsonFormatWriter(); + P_API JsonFormatWriter(); + P_API ~JsonFormatWriter(); // BEGIN Writer Interface - PIPE_API bool EnterNext(StringView name) override; - PIPE_API bool EnterNext() override; - PIPE_API void Leave() override; - - PIPE_API void BeginObject() override; - PIPE_API void BeginArray(u32 size) override; - - PIPE_API void Write(bool val) override; - PIPE_API void Write(i8 val) override; - PIPE_API void Write(u8 val) override; - PIPE_API void Write(i16 val) override; - PIPE_API void Write(u16 val) override; - PIPE_API void Write(i32 val) override; - PIPE_API void Write(u32 val) override; - PIPE_API void Write(i64 val) override; - PIPE_API void Write(u64 val) override; - PIPE_API void Write(float val) override; - PIPE_API void Write(double val) override; - PIPE_API void Write(StringView val) override; - - PIPE_API bool IsValid() const override + P_API bool EnterNext(StringView name) override; + P_API bool EnterNext() override; + P_API void Leave() override; + + P_API void BeginObject() override; + P_API void BeginArray(u32 size) override; + + P_API void Write(bool val) override; + P_API void Write(i8 val) override; + P_API void Write(u8 val) override; + P_API void Write(i16 val) override; + P_API void Write(u16 val) override; + P_API void Write(i32 val) override; + P_API void Write(u32 val) override; + P_API void Write(i64 val) override; + P_API void Write(u64 val) override; + P_API void Write(float val) override; + P_API void Write(double val) override; + P_API void Write(StringView val) override; + + P_API bool IsValid() const override { return doc != nullptr; } // END Writer Interface - PIPE_API void Close(); + P_API void Close(); - PIPE_API StringView ToString(bool pretty = true, bool ensureClosed = true); + P_API StringView ToString(bool pretty = true, bool ensureClosed = true); private: Scope& GetScope(); @@ -781,32 +794,32 @@ namespace p u8* pointer = nullptr; public: - PIPE_API BinaryFormatReader(TView data); - PIPE_API ~BinaryFormatReader(); - - PIPE_API void BeginObject() override {} // Nothing to do - PIPE_API void BeginArray(u32& size) override; - - PIPE_API bool EnterNext(StringView name) override; // Nothing to do - PIPE_API bool EnterNext() override; // Nothing to do - PIPE_API void Leave() override {} // Nothing to do - - PIPE_API void Read(bool& val) override; - PIPE_API void Read(i8& val) override; - PIPE_API void Read(u8& val) override; - PIPE_API void Read(i16& val) override; - PIPE_API void Read(u16& val) override; - PIPE_API void Read(i32& val) override; - PIPE_API void Read(u32& val) override; - PIPE_API void Read(i64& val) override; - PIPE_API void Read(u64& val) override; - PIPE_API void Read(float& val) override; - PIPE_API void Read(double& val) override; - PIPE_API void Read(StringView& val) override; - - PIPE_API bool IsObject() const override; - PIPE_API bool IsArray() const override; - PIPE_API bool IsValid() const override; + P_API BinaryFormatReader(TView data); + P_API ~BinaryFormatReader(); + + P_API void BeginObject() override {} // Nothing to do + P_API void BeginArray(u32& size) override; + + P_API bool EnterNext(StringView name) override; // Nothing to do + P_API bool EnterNext() override; // Nothing to do + P_API void Leave() override {} // Nothing to do + + P_API void Read(bool& val) override; + P_API void Read(i8& val) override; + P_API void Read(u8& val) override; + P_API void Read(i16& val) override; + P_API void Read(u16& val) override; + P_API void Read(i32& val) override; + P_API void Read(u32& val) override; + P_API void Read(i64& val) override; + P_API void Read(u64& val) override; + P_API void Read(float& val) override; + P_API void Read(double& val) override; + P_API void Read(StringView& val) override; + + P_API bool IsObject() const override; + P_API bool IsArray() const override; + P_API bool IsValid() const override; }; struct BinaryFormatWriter : public IFormatWriter @@ -814,39 +827,39 @@ namespace p private: Arena& arena; u8* data = nullptr; - i32 size = 0; - i32 capacity = 0; + u32 size = 0; + u32 capacity = 0; public: - PIPE_API BinaryFormatWriter(Arena& arena = p::GetCurrentArena()); - PIPE_API ~BinaryFormatWriter(); + P_API BinaryFormatWriter(Arena& arena = p::GetCurrentArena()); + P_API ~BinaryFormatWriter(); // BEGIN Writer Interface - PIPE_API void BeginObject() override {} // Nothing to do - PIPE_API void BeginArray(u32 size) override; // Nothing to do - PIPE_API bool EnterNext(StringView name) override; - PIPE_API bool EnterNext() override; - PIPE_API void Leave() override {} // Nothing to do - PIPE_API void Write(bool val) override; - PIPE_API void Write(i8 val) override; - PIPE_API void Write(u8 val) override; - PIPE_API void Write(i16 val) override; - PIPE_API void Write(u16 val) override; - PIPE_API void Write(i32 val) override; - PIPE_API void Write(u32 val) override; - PIPE_API void Write(i64 val) override; - PIPE_API void Write(u64 val) override; - PIPE_API void Write(float val) override; - PIPE_API void Write(double val) override; - PIPE_API void Write(StringView val) override; - PIPE_API bool IsValid() const override + P_API void BeginObject() override {} // Nothing to do + P_API void BeginArray(u32 size) override; // Nothing to do + P_API bool EnterNext(StringView name) override; + P_API bool EnterNext() override; + P_API void Leave() override {} // Nothing to do + P_API void Write(bool val) override; + P_API void Write(i8 val) override; + P_API void Write(u8 val) override; + P_API void Write(i16 val) override; + P_API void Write(u16 val) override; + P_API void Write(i32 val) override; + P_API void Write(u32 val) override; + P_API void Write(i64 val) override; + P_API void Write(u64 val) override; + P_API void Write(float val) override; + P_API void Write(double val) override; + P_API void Write(StringView val) override; + P_API bool IsValid() const override { return data != nullptr; } // END Writer Interface - PIPE_API TView GetData(); + P_API TView GetData(); private: void PreAlloc(p::u32 offset); @@ -861,36 +874,36 @@ namespace p struct Vec; struct Quat; - PIPE_API void Read(Reader& ct, String& val); - PIPE_API void Write(Writer& ct, const String& val); - PIPE_API void Read(Reader& ct, Tag& val); - PIPE_API void Write(Writer& ct, const Tag& val); - - PIPE_API void Read(Reader& ct, Guid& guid); - PIPE_API void Write(Writer& ct, const Guid& guid); - - PIPE_API void Read(Reader& ct, TColor& color); - PIPE_API void Write(Writer& ct, const TColor& color); - PIPE_API void Read(Reader& ct, TColor& color); - PIPE_API void Write(Writer& ct, const TColor& color); - PIPE_API void Read(Reader& ct, TColor& color); - PIPE_API void Write(Writer& ct, const TColor& color); - PIPE_API void Read(Reader& r, TColor& color); - PIPE_API void Write(Writer& w, const TColor& color); - - PIPE_API void Read(Reader& ct, Vec<2, float>& val); - PIPE_API void Write(Writer& ct, const Vec<2, float>& val); - PIPE_API void Read(Reader& ct, Vec<2, u32>& val); - PIPE_API void Write(Writer& ct, const Vec<2, u32>& val); - PIPE_API void Read(Reader& ct, Vec<2, i32>& val); - PIPE_API void Write(Writer& ct, const Vec<2, i32>& val); - PIPE_API void Read(Reader& ct, Vec<3, float>& val); - PIPE_API void Write(Writer& ct, const Vec<3, float>& val); - PIPE_API void Read(Reader& ct, Vec<3, u32>& val); - PIPE_API void Write(Writer& ct, const Vec<3, u32>& val); - PIPE_API void Read(Reader& ct, Vec<3, i32>& val); - PIPE_API void Write(Writer& ct, const Vec<3, i32>& val); - PIPE_API void Read(Reader& ct, Quat& val); - PIPE_API void Write(Writer& ct, const Quat& val); + P_API void Read(Reader& ct, String& val); + P_API void Write(Writer& ct, const String& val); + P_API void Read(Reader& ct, Tag& val); + P_API void Write(Writer& ct, const Tag& val); + + P_API void Read(Reader& ct, Guid& guid); + P_API void Write(Writer& ct, const Guid& guid); + + P_API void Read(Reader& ct, TColor& color); + P_API void Write(Writer& ct, const TColor& color); + P_API void Read(Reader& ct, TColor& color); + P_API void Write(Writer& ct, const TColor& color); + P_API void Read(Reader& ct, TColor& color); + P_API void Write(Writer& ct, const TColor& color); + P_API void Read(Reader& r, TColor& color); + P_API void Write(Writer& w, const TColor& color); + + P_API void Read(Reader& ct, Vec<2, float>& val); + P_API void Write(Writer& ct, const Vec<2, float>& val); + P_API void Read(Reader& ct, Vec<2, u32>& val); + P_API void Write(Writer& ct, const Vec<2, u32>& val); + P_API void Read(Reader& ct, Vec<2, i32>& val); + P_API void Write(Writer& ct, const Vec<2, i32>& val); + P_API void Read(Reader& ct, Vec<3, float>& val); + P_API void Write(Writer& ct, const Vec<3, float>& val); + P_API void Read(Reader& ct, Vec<3, u32>& val); + P_API void Write(Writer& ct, const Vec<3, u32>& val); + P_API void Read(Reader& ct, Vec<3, i32>& val); + P_API void Write(Writer& ct, const Vec<3, i32>& val); + P_API void Read(Reader& ct, Quat& val); + P_API void Write(Writer& ct, const Quat& val); #pragma endregion CoreSupport } // namespace p diff --git a/Include/PipeSerializeFwd.h b/Include/PipeSerializeFwd.h index f0c374d5..df49c831 100644 --- a/Include/PipeSerializeFwd.h +++ b/Include/PipeSerializeFwd.h @@ -1,7 +1,7 @@ // Copyright 2015-2024 Piperift - All rights reserved #pragma once -#include "Pipe/Core/Platform.h" +#include "PipePlatform.h" // Forward declarations of all serialization diff --git a/Include/PipeTime.h b/Include/PipeTime.h index 788c86dc..18d7a096 100644 --- a/Include/PipeTime.h +++ b/Include/PipeTime.h @@ -3,8 +3,8 @@ #pragma once #include "Pipe/Core/Checks.h" -#include "Pipe/Core/Platform.h" #include "Pipe/Core/String.h" +#include "PipePlatform.h" #include @@ -117,7 +117,7 @@ namespace p * * @see DateTime */ - struct PIPE_API Timespan + struct P_API Timespan { private: #pragma warning(push) @@ -621,8 +621,8 @@ namespace p * @return String representation. * @see Parse */ - String ToString(const TChar* Format) const; - void ToString(const TChar* format, String& result) const; + String ToString(const char* Format) const; + void ToString(const char* format, String& result) const; public: /** @@ -742,7 +742,7 @@ namespace p * @see FDateRange * @see FTimespan */ - struct PIPE_API DateTime + struct P_API DateTime { protected: /** Holds the days per month in a non-leap year. */ @@ -1138,8 +1138,8 @@ namespace p * @return String representation. * @see Parse, ToIso8601 */ - String ToString(const TChar* Format) const; - void ToString(const TChar* format, String& result) const; + String ToString(const char* Format) const; + void ToString(const char* format, String& result) const; /** * Returns this date as the number of seconds since the Unix Epoch (January @@ -1296,7 +1296,7 @@ namespace p * @return true if the string was converted successfully, false otherwise. * @see Parse, ParseHttpDate, ToIso8601 */ - static bool ParseIso8601(const TChar* DateTimeString, DateTime& OutDateTime); + static bool ParseIso8601(const char* DateTimeString, DateTime& OutDateTime); /** * Gets the local date on this computer. @@ -1365,7 +1365,9 @@ namespace p epsilon = delta; } if (++itercnt >= limit) + { break; + } } while (epsilon > tolerance); return dst_now + (tp - src_now); @@ -1376,7 +1378,7 @@ namespace p }; - struct PIPE_API FrameTime + struct P_API FrameTime { protected: DateTime previousTime; diff --git a/Include/PipeVectors.h b/Include/PipeVectors.h index 3d611b12..46c3cd25 100644 --- a/Include/PipeVectors.h +++ b/Include/PipeVectors.h @@ -710,12 +710,16 @@ namespace p for (u32 i = 0; i < size; ++i) { if (point[i] < min[i]) + { min[i] = point[i]; + } } for (u32 i = 0; i < size; ++i) { if (point[i] > max[i]) + { max[i] = point[i]; + } } } void Merge(const TAABB& other) @@ -723,12 +727,16 @@ namespace p for (u32 i = 0; i < size; ++i) { if (other.min[i] < min[i]) + { min[i] = other.min[i]; + } } for (u32 i = 0; i < size; ++i) { if (other.max[i] > max[i]) + { max[i] = other.max[i]; + } } } @@ -739,12 +747,16 @@ namespace p for (u32 i = 0; i < size; ++i) { if (other.min[i] > min[i]) + { min[i] = other.min[i]; + } } for (u32 i = 0; i < size; ++i) { if (other.max[i] < max[i]) + { max[i] = other.max[i]; + } } } @@ -774,49 +786,73 @@ namespace p constexpr bool Contains(const Vec& p) const { if constexpr (size == 2) + { return (p.x >= min.x && p.y >= min.y) && (p.x < max.x && p.y < max.y); + } else if constexpr (size == 3) + { return (p.x >= min.x && p.y >= min.y && p.z >= min.z) && (p.x < max.x && p.y < max.y && p.z < max.z); + } else if constexpr (size == 4) + { return (p.x >= min.x && p.y >= min.y && p.z >= min.z && p.w >= min.w) && (p.x < max.x && p.y < max.y && p.z < max.z && p.w < max.w); + } } constexpr bool Contains(const TAABB& r) const requires(size == 2) { if constexpr (size == 2) + { return (r.min.x >= min.x && r.min.y >= min.y) && (r.max.x <= max.x && r.max.y <= max.y); + } else if constexpr (size == 3) + { return (r.min.x >= min.x && r.min.y >= min.y && r.min.z >= min.z) && (r.max.x <= max.x && r.max.y <= max.y && r.max.z <= max.z); + } else if constexpr (size == 4) + { return (r.min.x >= min.x && r.min.y >= min.y && r.min.z >= min.z && r.min.w >= min.w) && (r.max.x <= max.x && r.max.y <= max.y && r.max.z <= max.z && r.max.w <= max.w); + } } constexpr bool Overlaps(const Vec& p) const { if constexpr (size == 2) + { return (p.x > min.x && p.y > min.y) && (p.x < max.x && p.y < max.y); + } else if constexpr (size == 3) + { return (p.x > min.x && p.y > min.y && p.z > min.z) && (p.x < max.x && p.y < max.y && p.z < max.z); + } else if constexpr (size == 4) + { return (p.x > min.x && p.y > min.y && p.z > min.z && p.w > min.w) && (p.x < max.x && p.y < max.y && p.z < max.z && p.w < max.w); + } } constexpr bool Overlaps(const TAABB& r) const { if constexpr (size == 2) + { return (r.min.x < max.x && r.min.y < max.y) && (r.max.x > min.x && r.max.y > min.y); + } else if constexpr (size == 3) + { return (r.min.x < max.x && r.min.y < max.y && r.min.z < max.z) && (r.max.x > min.x && r.max.y > min.y && r.max.z > min.z); + } else if constexpr (size == 4) + { return (r.min.x < max.x && r.min.y < max.y && r.min.z < max.z && r.min.w < max.w) && (r.max.x > min.x && r.max.y > min.y && r.max.z > min.z && r.max.w > min.w); + } } constexpr Vec GetSize() const @@ -855,11 +891,17 @@ namespace p constexpr bool IsInverted() const { if constexpr (size == 2) + { return min.x > max.x || min.y > max.y; + } else if constexpr (size == 3) + { return min.x > max.x || min.y > max.y || min.z > max.z; + } else if constexpr (size == 4) + { return min.x > max.x || min.y > max.y || min.z > max.z || min.w > max.w; + } } }; @@ -883,15 +925,15 @@ namespace p // however, in practice if you have 0 scale, and relative transform doesn't make much // sense anymore because you should be instead of showing gigantic infinite mesh also // returning BIG_NUMBER causes sequential NaN issues by multiplying so we hardcode as 0 - PIPE_API v3 GetSafeScaleReciprocal(const v3& scale, float tolerance = smallNumber); - PIPE_API v2 ClosestPointInLine(v2 a, v2 b, v2 point); - PIPE_API v3 ClosestPointInLine(v3 a, v3 b, v3 point); + P_API v3 GetSafeScaleReciprocal(const v3& scale, float tolerance = smallNumber); + P_API v2 ClosestPointInLine(v2 a, v2 b, v2 point); + P_API v3 ClosestPointInLine(v3 a, v3 b, v3 point); } // namespace Vectors - PIPE_API v2 EvaluateCubicBezier(v2 p0, v2 p1, v2 p2, v2 p3, float t); + P_API v2 EvaluateCubicBezier(v2 p0, v2 p1, v2 p2, v2 p3, float t); - struct PIPE_API Rotator : public v3 + struct P_API Rotator : public v3 { using v3::v3; @@ -943,7 +985,7 @@ namespace p }; - struct PIPE_API Quat + struct P_API Quat { public: float x; diff --git a/Src/Core/Guid.cpp b/Src/Core/Guid.cpp index a4cba8c9..88e87644 100644 --- a/Src/Core/Guid.cpp +++ b/Src/Core/Guid.cpp @@ -3,7 +3,7 @@ #include "Pipe/Core/Guid.h" #include "Pipe/Core/Char.h" -#include "Pipe/Core/PlatformMisc.h" +#include "PipePlatform.h" #include @@ -18,27 +18,27 @@ namespace p switch (Format) { case EGuidFormats::DigitsWithHyphens: - return Strings::Format(TX("{:08X}-{:04X}-{:04X}-{:04X}-{:04X}{:08X}"), a, b >> 16, + return Strings::Format("{:08X}-{:04X}-{:04X}-{:04X}-{:04X}{:08X}", a, b >> 16, b & 0xFFFF, c >> 16, c & 0xFFFF, d); case EGuidFormats::DigitsWithHyphensInBraces: - return Strings::Format(TX("{{{:08X}-{:04X}-{:04X}-{:04X}-{:04X}{:08X}}}"), a, - b >> 16, b & 0xFFFF, c >> 16, c & 0xFFFF, d); + return Strings::Format("{{{:08X}-{:04X}-{:04X}-{:04X}-{:04X}{:08X}}}", a, b >> 16, + b & 0xFFFF, c >> 16, c & 0xFFFF, d); case EGuidFormats::DigitsWithHyphensInParentheses: - return Strings::Format(TX("({:08X}-{:04X}-{:04X}-{:04X}-{:04X}{:08X})"), a, b >> 16, + return Strings::Format("({:08X}-{:04X}-{:04X}-{:04X}-{:04X}{:08X})", a, b >> 16, b & 0xFFFF, c >> 16, c & 0xFFFF, d); case EGuidFormats::HexValuesInBraces: return Strings::Format( - TX("{{0x{:08X},0x{:04X},0x{:04X},{{0x{:02X},0x{:02X},0x{:02X}," - "0x{:02X},0x{:02X},0x{:02X},0x{:02X},0x{:02X}}}}}"), + "{{0x{:08X},0x{:04X},0x{:04X},{{0x{:02X},0x{:02X},0x{:02X}," + "0x{:02X},0x{:02X},0x{:02X},0x{:02X},0x{:02X}}}}}", a, b >> 16, b & 0xFFFF, c >> 24, (c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0XFF, d >> 24, (d >> 16) & 0XFF, (d >> 8) & 0XFF, d & 0XFF); case EGuidFormats::UniqueObjectGuid: - return Strings::Format(TX("{:08X}-{:08X}-{:08X}-{:08X}"), a, b, c, d); - default: return Strings::Format(TX("{:08X}{:08X}{:08X}{:08X}"), a, b, c, d); + return Strings::Format("{:08X}-{:08X}-{:08X}-{:08X}", a, b, c, d); + default: return Strings::Format("{:08X}{:08X}{:08X}{:08X}", a, b, c, d); } } @@ -69,7 +69,7 @@ namespace p if (stringSize == 38) { - if (guidString[0] == TX('{')) + if (guidString[0] == '{') { return ParseExact(guidString, EGuidFormats::DigitsWithHyphensInBraces, OutGuid); } @@ -102,8 +102,8 @@ namespace p } else if (Format == EGuidFormats::DigitsWithHyphens) { - if ((GuidString[8] != TX('-')) || (GuidString[13] != TX('-')) - || (GuidString[18] != TX('-')) || (GuidString[23] != TX('-'))) + if ((GuidString[8] != '-') || (GuidString[13] != '-') || (GuidString[18] != '-') + || (GuidString[23] != '-')) { return false; } @@ -116,9 +116,8 @@ namespace p } else if (Format == EGuidFormats::DigitsWithHyphensInBraces) { - if ((GuidString[0] != TX('{')) || (GuidString[9] != TX('-')) - || (GuidString[14] != TX('-')) || (GuidString[19] != TX('-')) - || (GuidString[24] != TX('-')) || (GuidString[37] != TX('}'))) + if ((GuidString[0] != '{') || (GuidString[9] != '-') || (GuidString[14] != '-') + || (GuidString[19] != '-') || (GuidString[24] != '-') || (GuidString[37] != '}')) { return false; } @@ -131,9 +130,8 @@ namespace p } else if (Format == EGuidFormats::DigitsWithHyphensInParentheses) { - if ((GuidString[0] != TX('(')) || (GuidString[9] != TX('-')) - || (GuidString[14] != TX('-')) || (GuidString[19] != TX('-')) - || (GuidString[24] != TX('-')) || (GuidString[37] != TX(')'))) + if ((GuidString[0] != '(') || (GuidString[9] != '-') || (GuidString[14] != '-') + || (GuidString[19] != '-') || (GuidString[24] != '-') || (GuidString[37] != ')')) { return false; } @@ -146,24 +144,18 @@ namespace p } else if (Format == EGuidFormats::HexValuesInBraces) { - if ((GuidString[0] != TX('{')) || (GuidString[1] != TX('0')) - || (GuidString[2] != TX('x')) || (GuidString[11] != TX(',')) - || (GuidString[12] != TX('0')) || (GuidString[13] != TX('x')) - || (GuidString[18] != TX(',')) || (GuidString[19] != TX('0')) - || (GuidString[20] != TX('x')) || (GuidString[25] != TX(',')) - || (GuidString[26] != TX('{')) || (GuidString[27] != TX('0')) - || (GuidString[28] != TX('x')) || (GuidString[31] != TX(',')) - || (GuidString[32] != TX('0')) || (GuidString[33] != TX('x')) - || (GuidString[36] != TX(',')) || (GuidString[37] != TX('0')) - || (GuidString[38] != TX('x')) || (GuidString[41] != TX(',')) - || (GuidString[42] != TX('0')) || (GuidString[43] != TX('x')) - || (GuidString[46] != TX(',')) || (GuidString[47] != TX('0')) - || (GuidString[48] != TX('x')) || (GuidString[51] != TX(',')) - || (GuidString[52] != TX('0')) || (GuidString[53] != TX('x')) - || (GuidString[56] != TX(',')) || (GuidString[57] != TX('0')) - || (GuidString[58] != TX('x')) || (GuidString[61] != TX(',')) - || (GuidString[62] != TX('0')) || (GuidString[63] != TX('x')) - || (GuidString[66] != TX('}')) || (GuidString[67] != TX('}'))) + if ((GuidString[0] != '{') || (GuidString[1] != '0') || (GuidString[2] != 'x') + || (GuidString[11] != ',') || (GuidString[12] != '0') || (GuidString[13] != 'x') + || (GuidString[18] != ',') || (GuidString[19] != '0') || (GuidString[20] != 'x') + || (GuidString[25] != ',') || (GuidString[26] != '{') || (GuidString[27] != '0') + || (GuidString[28] != 'x') || (GuidString[31] != ',') || (GuidString[32] != '0') + || (GuidString[33] != 'x') || (GuidString[36] != ',') || (GuidString[37] != '0') + || (GuidString[38] != 'x') || (GuidString[41] != ',') || (GuidString[42] != '0') + || (GuidString[43] != 'x') || (GuidString[46] != ',') || (GuidString[47] != '0') + || (GuidString[48] != 'x') || (GuidString[51] != ',') || (GuidString[52] != '0') + || (GuidString[53] != 'x') || (GuidString[56] != ',') || (GuidString[57] != '0') + || (GuidString[58] != 'x') || (GuidString[61] != ',') || (GuidString[62] != '0') + || (GuidString[63] != 'x') || (GuidString[66] != '}') || (GuidString[67] != '}')) { return false; } @@ -182,8 +174,7 @@ namespace p } else if (Format == EGuidFormats::UniqueObjectGuid) { - if ((GuidString[8] != TX('-')) || (GuidString[17] != TX('-')) - || (GuidString[26] != TX('-'))) + if ((GuidString[8] != '-') || (GuidString[17] != '-') || (GuidString[26] != '-')) { return false; } diff --git a/Src/Core/Hash.cpp b/Src/Core/Hash.cpp index e02a7a6b..90d9e083 100644 --- a/Src/Core/Hash.cpp +++ b/Src/Core/Hash.cpp @@ -3,17 +3,19 @@ #include "Pipe/Core/Hash.h" -#include "Pipe/Memory/Memory.h" +#include "PipeMemory.h" namespace p { -#if __has_cpp_attribute(clang::fallthrough) - #define P_FALLTHROUGH() [[clang::fallthrough]] -#elif __has_cpp_attribute(gnu::fallthrough) - #define P_FALLTHROUGH() [[gnu::fallthrough]] -#else - #define P_FALLTHROUGH() +#ifndef P_FALLTHROUGH + #if __has_cpp_attribute(clang::fallthrough) + #define P_FALLTHROUGH [[clang::fallthrough]] + #elif __has_cpp_attribute(gnu::fallthrough) + #define P_FALLTHROUGH [[gnu::fallthrough]] + #else + #define P_FALLTHROUGH + #endif #endif @@ -52,16 +54,16 @@ namespace p const auto* const data8 = reinterpret_cast(data64 + n_blocks); switch (len & 7U) { - case 7: h ^= static_cast(data8[6]) << 48U; P_FALLTHROUGH(); - case 6: h ^= static_cast(data8[5]) << 40U; P_FALLTHROUGH(); - case 5: h ^= static_cast(data8[4]) << 32U; P_FALLTHROUGH(); - case 4: h ^= static_cast(data8[3]) << 24U; P_FALLTHROUGH(); - case 3: h ^= static_cast(data8[2]) << 16U; P_FALLTHROUGH(); - case 2: h ^= static_cast(data8[1]) << 8U; P_FALLTHROUGH(); + case 7: h ^= static_cast(data8[6]) << 48U; P_FALLTHROUGH; + case 6: h ^= static_cast(data8[5]) << 40U; P_FALLTHROUGH; + case 5: h ^= static_cast(data8[4]) << 32U; P_FALLTHROUGH; + case 4: h ^= static_cast(data8[3]) << 24U; P_FALLTHROUGH; + case 3: h ^= static_cast(data8[2]) << 16U; P_FALLTHROUGH; + case 2: h ^= static_cast(data8[1]) << 8U; P_FALLTHROUGH; case 1: h ^= static_cast(data8[0]); h *= m; - P_FALLTHROUGH(); + P_FALLTHROUGH; default: break; } diff --git a/Src/Core/Log.cpp b/Src/Core/Log.cpp index 26241468..fe734f42 100644 --- a/Src/Core/Log.cpp +++ b/Src/Core/Log.cpp @@ -11,34 +11,65 @@ namespace p { - void InitLog(StringView logPath) {} + // clang-format off + const Logger defaultLogger = Logger{ + .infoCallback = [](StringView msg) { + String text; + auto now = DateTime::Now(); + now.ToString("[%Y/%m/%d %H:%M:%S]", text); + Strings::FormatTo(text, "[Info] {}\n", msg); + std::cout << text; + }, + .warningCallback = [](StringView msg) { + String text; + auto now = DateTime::Now(); + now.ToString("[%Y/%m/%d %H:%M:%S]", text); + Strings::FormatTo(text, "[Warning] {}\n", msg); + std::cout << text; + }, + .errorCallback = [](StringView msg) { + String text; + auto now = DateTime::Now(); + now.ToString("[%Y/%m/%d %H:%M:%S]", text); + Strings::FormatTo(text, "[Error] {}\n", msg); + std::cout << text; + } + }; + // clang-format on - void ShutdownLog() {} + const Logger* globalLogger = nullptr; + + void InitLog(Logger* logger) + { + globalLogger = logger ? logger : &defaultLogger; + } + + void ShutdownLog() + { + globalLogger = nullptr; + } void Info(StringView msg) { - String text; - auto now = DateTime::Now(); - now.ToString("[%Y/%m/%d %H:%M:%S]", text); - Strings::FormatTo(text, "[Info] {}", msg); - std::cout << text << std::endl; + if (globalLogger) + { + globalLogger->infoCallback(msg); + } } void Warning(StringView msg) { - String text; - auto now = DateTime::Now(); - now.ToString("[%Y/%m/%d %H:%M:%S]", text); - Strings::FormatTo(text, "[Warning] {}", msg); - std::cout << text << std::endl; + if (globalLogger) + { + globalLogger->warningCallback(msg); + } } void Error(StringView msg) { - String text; - auto now = DateTime::Now(); - now.ToString("[%Y/%m/%d %H:%M:%S]", text); - Strings::FormatTo(text, "[Error] {}", msg); - std::cout << text << std::endl; + if (globalLogger) + { + globalLogger->errorCallback(msg); + } } } // namespace p diff --git a/Src/Core/String.cpp b/Src/Core/String.cpp index 90e9f1c6..c4ef0ae5 100644 --- a/Src/Core/String.cpp +++ b/Src/Core/String.cpp @@ -19,16 +19,16 @@ namespace p::Strings String result; result.reserve(value.size()); - const TChar* p = value.data(); - const TChar* end = value.data() + value.size(); - const TChar* last = end - 1; + const char* p = value.data(); + const char* end = value.data() + value.size(); + const char* last = end - 1; for (; p < last; ++p) { - const TChar* next = p + 1; + const char* next = p + 1; if (FChar::IsAlpha(*p) && (FChar::IsUpper(*next) || FChar::IsDigit(*next))) { result.push_back(*p); - result.push_back(TX(' ')); + result.push_back(' '); } else { @@ -57,7 +57,7 @@ namespace p::Strings } } - bool RemoveCharFromEnd(String& str, TChar c) + bool RemoveCharFromEnd(String& str, char c) { if (EndsWith(str, c)) { @@ -67,7 +67,7 @@ namespace p::Strings return false; } - i32 Split(const String& str, TArray& tokens, const TChar delim) + i32 Split(const String& str, TArray& tokens, const char delim) { sizet current, previous = 0; current = str.find(delim); @@ -81,7 +81,7 @@ namespace p::Strings return tokens.Size(); } - bool Split(const String& str, String& a, String& b, const TChar* delim) + bool Split(const String& str, String& a, String& b, const char* delim) { const sizet pos = str.find(delim); if (pos != String::npos) @@ -98,17 +98,17 @@ namespace p::Strings return IsNumeric(str.data()); } - bool IsNumeric(const TChar* Str) + bool IsNumeric(const char* Str) { - if (*Str == TX('-') || *Str == TX('+')) + if (*Str == '-' || *Str == '+') { Str++; } bool bHasDot = false; - while (*Str != TX('\0')) + while (*Str != '\0') { - if (*Str == TX('.')) + if (*Str == '.') { if (bHasDot) { diff --git a/Src/Core/StringView.cpp b/Src/Core/StringView.cpp index 344e5f8d..0a8fe049 100644 --- a/Src/Core/StringView.cpp +++ b/Src/Core/StringView.cpp @@ -2,8 +2,6 @@ #include "Pipe/Core/StringView.h" -#include "Pipe/Extern/fast_float.h" - namespace p::Strings { @@ -12,9 +10,9 @@ namespace p::Strings { T val; #if __cpp_lib_to_chars >= 202306L - if (fast_float::from_chars(str.data(), str.data() + str.size(), val)) + if (std::from_chars(str.data(), str.data() + str.size(), val)) #else - if (fast_float::from_chars(str.data(), str.data() + str.size(), val).ec == std::errc{}) + if (std::from_chars(str.data(), str.data() + str.size(), val).ec == std::errc{}) #endif { return val; diff --git a/Src/Core/Subprocess.cpp b/Src/Core/Subprocess.cpp index ecb37827..32462067 100644 --- a/Src/Core/Subprocess.cpp +++ b/Src/Core/Subprocess.cpp @@ -3,10 +3,10 @@ #include "Pipe/Core/Log.h" #include "Pipe/Core/Optional.h" -#include "Pipe/Core/PlatformMisc.h" #include "Pipe/Core/String.h" #include "Pipe/Core/StringView.h" #include "PipeArrays.h" +#include "PipePlatform.h" #if defined(_MSC_VER) @@ -81,20 +81,20 @@ namespace p Subprocess::Subprocess(Subprocess&& other) noexcept { - Swap(cinFile, other.cinFile); - Swap(coutFile, other.coutFile); - Swap(cerrFile, other.cerrFile); + p::Swap(cinFile, other.cinFile); + p::Swap(coutFile, other.coutFile); + p::Swap(cerrFile, other.cerrFile); #if defined(_MSC_VER) - Swap(hProcess, other.hProcess); - Swap(hStdInput, other.hStdInput); - Swap(hEventOutput, other.hEventOutput); - Swap(hEventError, other.hEventError); + p::Swap(hProcess, other.hProcess); + p::Swap(hStdInput, other.hStdInput); + p::Swap(hEventOutput, other.hEventOutput); + p::Swap(hEventError, other.hEventError); #else - Swap(child, other.child); - Swap(returnStatus, other.returnStatus); + p::Swap(child, other.child); + p::Swap(returnStatus, other.returnStatus); #endif - Swap(options, other.options); - Swap(alive, other.alive); + p::Swap(options, other.options); + p::Swap(alive, other.alive); } Subprocess::~Subprocess() @@ -108,6 +108,30 @@ namespace p #if defined(_MSC_VER) + const char* GetSystemErrorMessage(char* buffer, i32 size, i32 error) + { + P_Check(buffer && size); + + *buffer = '\0'; + if (error == 0) + { + error = ::GetLastError(); + } + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, size, nullptr); + char* found = std::strchr(buffer, '\r'); + if (found) + { + *found = '\0'; + } + found = std::strchr(buffer, '\n'); + if (found) + { + *found = '\0'; + } + return buffer; + } + i32 RunProcessNamedPipeHelper(void** readPipe, void** writePipe) { const u64 pipeAccessInbound = 0x00000001; @@ -164,7 +188,6 @@ namespace p instance.options = options; #if defined(_MSC_VER) - sizet len; i32 i, j; u64 flags = 0; static constexpr u64 startFUseStdHandles = 0x00000100; @@ -360,8 +383,8 @@ namespace p reinterpret_cast<_PROCESS_INFORMATION*>(&processInfo))) { DWORD errorCode = GetLastError(); - TChar errorMessage[512]; - PlatformMisc::GetSystemErrorMessage(errorMessage, 512, errorCode); + char errorMessage[512]; + GetSystemErrorMessage(errorMessage, 512, errorCode); Error("RunProcess failed: {} (0x{:08x})", errorMessage, errorCode); return {}; } diff --git a/Src/Core/Tag.cpp b/Src/Core/Tag.cpp index a55d93e9..a5d29571 100644 --- a/Src/Core/Tag.cpp +++ b/Src/Core/Tag.cpp @@ -2,8 +2,7 @@ #include "Pipe/Core/Tag.h" -#include "Pipe/Memory/Memory.h" -#include "Pipe/Memory/MultiLinearArena.h" +#include "PipeMemoryArenas.h" #include #include @@ -17,15 +16,15 @@ namespace p u32 size; TagHeader(const TagHeader&) = delete; - const TChar* Data() const + const char* Data() const { - return reinterpret_cast(this + 1); + return reinterpret_cast(this + 1); }; }; - TagHeader* GetTagHeader(const TChar* data) + TagHeader* GetTagHeader(const char* data) { - return reinterpret_cast(const_cast(data)) - 1; + return reinterpret_cast(const_cast(data)) - 1; } struct TagHeaderRef @@ -85,7 +84,9 @@ namespace p Tag& Tag::operator=(const Tag& other) { if (this == &other) + { return *this; + } InternalReset(); hash = other.hash; @@ -99,7 +100,9 @@ namespace p Tag& Tag::operator=(Tag&& other) noexcept { if (this == &other) + { return *this; + } InternalReset(); hash = other.hash; @@ -113,7 +116,7 @@ namespace p InternalReset(); } - const TChar* Tag::Data() const + const char* Tag::Data() const { return str; } @@ -133,7 +136,7 @@ namespace p constexpr sizet GetAllocSize(sizet dataSize) { // +1 for the end character of the string - return sizeof(TagHeader) + sizeof(TChar) * (dataSize + 1); + return sizeof(TagHeader) + sizeof(char) * (dataSize + 1); } i32 Tag::FlushInactiveTags() @@ -213,8 +216,8 @@ namespace p header->activeTags = 0; header->size = size; // Copy string data - auto* const data = const_cast(header->Data()); - p::CopyMem(data, (void*)value.data(), sizeof(TChar) * size); + auto* const data = const_cast(header->Data()); + p::CopyMem(data, (void*)value.data(), sizeof(char) * size); data[header->size] = '\0'; std::unique_lock lock{stringsListMutex}; diff --git a/Src/Core/WindowsPlatformMisc.cpp b/Src/Core/WindowsPlatformMisc.cpp deleted file mode 100644 index 5fe2b175..00000000 --- a/Src/Core/WindowsPlatformMisc.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved - -#if P_PLATFORM_WINDOWS - #include "Pipe/Core/WindowsPlatformMisc.h" - - #include "Pipe/Core/Checks.h" - #include - - #include - - -namespace p -{ - const TChar* WindowsPlatformMisc::GetSystemErrorMessage(TChar* buffer, i32 size, i32 error) - { - P_Check(buffer && size); - - *buffer = '\0'; - if (error == 0) - { - error = ::GetLastError(); - } - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, size, nullptr); - TChar* found = std::strchr(buffer, '\r'); - if (found) - { - *found = '\0'; - } - found = std::strchr(buffer, '\n'); - if (found) - { - *found = '\0'; - } - return buffer; - } -} // namespace p -#endif diff --git a/Src/Files/Files.cpp b/Src/Files/Files.cpp index 1e7a59fa..86d836c4 100644 --- a/Src/Files/Files.cpp +++ b/Src/Files/Files.cpp @@ -108,7 +108,7 @@ namespace p result.reserve(size + extraPadding); file.seekg(0, std::ios::beg); - result.assign(std::istreambuf_iterator(file), std::istreambuf_iterator()); + result.assign(std::istreambuf_iterator(file), std::istreambuf_iterator()); } return !result.empty(); } @@ -120,7 +120,7 @@ namespace p return false; } - std::basic_ofstream file(String{path}); + std::basic_ofstream file(String{path}); file.write(data.data(), data.size()); file.close(); return true; diff --git a/Src/Files/Paths.cpp b/Src/Files/Paths.cpp index 263e7f11..a1d9474a 100644 --- a/Src/Files/Paths.cpp +++ b/Src/Files/Paths.cpp @@ -19,7 +19,7 @@ #include #endif -/** Remote file systems codes */ + /** Remote file systems codes */ #define P_MAGIC_AFS 0x5346414F #define P_MAGIC_AUFS 0x61756673 #define P_MAGIC_CEPH 0x00C36400 @@ -56,7 +56,7 @@ namespace p #pragma region Internal namespace details { - bool IsDriveLetter(const TChar c) + bool IsDriveLetter(const char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } @@ -77,7 +77,7 @@ namespace p #if P_PLATFORM_WINDOWS - bool HasDriveLetterPrefix(const TChar* const first, const TChar* const last) + bool HasDriveLetterPrefix(const char* const first, const char* const last) { // test if [first, last) has a prefix of the form X: if (last - first >= 2 && FChar::ToUpper(first[0]) >= 'A' @@ -89,7 +89,7 @@ namespace p } #endif - const TChar* FindRootNameEnd(const TChar* const first, const TChar* const last) + const char* FindRootNameEnd(const char* const first, const char* const last) { const sizet len = last - first; #if P_PLATFORM_WINDOWS @@ -163,7 +163,7 @@ namespace p if (len > 2 && IsSeparator(first[0]) && IsSeparator(first[1]) && !IsSeparator(first[2]) && std::isprint(first[2])) { - const TChar* c = first + 3; + const char* c = first + 3; while (c <= last) { if (IsSeparator(*c)) @@ -183,21 +183,21 @@ namespace p #pragma endregion Internal - const TChar* FindRelativeChar(const TChar* const first, const TChar* const last) + const char* FindRelativeChar(const char* const first, const char* const last) { - const TChar* nameEnd; + const char* nameEnd; return FindRelativeChar(first, last, nameEnd); } - const TChar* FindRelativeChar( - const TChar* const first, const TChar* const last, const TChar*& outNameEnd) + const char* FindRelativeChar( + const char* const first, const char* const last, const char*& outNameEnd) { outNameEnd = details::FindRootNameEnd(first, last); // attempt to parse [first, last) as a path and return the start of relative-path return std::find_if_not(outNameEnd, last, IsSeparator); } - const TChar* FindFilename(const TChar* const first, const TChar* last) + const char* FindFilename(const char* const first, const char* last) { // attempt to parse [first, last) as a path and return the start of filename if it // exists; otherwise, last @@ -209,17 +209,17 @@ namespace p return last; } - const TChar* FindExtension(const TChar* const first, const TChar* const last) + const char* FindExtension(const char* const first, const char* const last) { - const TChar* filenameFirst = FindFilename(first, last); - const sizet filenameSize = last - filenameFirst; + const char* filenameFirst = FindFilename(first, last); + const sizet filenameSize = last - filenameFirst; if (filenameSize > 0u && // Check for "." and ".." filenames !(filenameFirst[0] == '.' && (filenameSize == 1u || (filenameSize == 2u && filenameFirst[1u] == '.')))) { - const TChar* extensionFirst = last; + const char* extensionFirst = last; while (extensionFirst > filenameFirst) { --extensionFirst; @@ -288,9 +288,9 @@ namespace p { // attempt to parse path as a path and return the filename if it exists; otherwise, an // empty view - const TChar* first = path.data(); - const TChar* last = first + path.size(); - const TChar* filename = FindFilename(first, last); + const char* first = path.data(); + const char* last = first + path.size(); + const char* filename = FindFilename(first, last); return StringView{filename, static_cast(last - filename)}; } @@ -301,25 +301,25 @@ namespace p void RemoveFilename(String& path) { - const TChar* first = path.data(); - const TChar* last = first + path.size(); - const TChar* filename = FindFilename(first, last); + const char* first = path.data(); + const char* last = first + path.size(); + const char* filename = FindFilename(first, last); path.erase(static_cast(filename - first)); } void RemoveFilename(StringView& path) { - const TChar* first = path.data(); - const TChar* last = first + path.size(); - const TChar* filename = FindFilename(first, last); - path = StringView{first, static_cast(filename - first)}; + const char* first = path.data(); + const char* last = first + path.size(); + const char* filename = FindFilename(first, last); + path = StringView{first, static_cast(filename - first)}; } StringView GetStem(StringView path) { - const TChar* first = path.data(); - const TChar* last = first + path.size(); - const TChar* stemFirst = FindFilename(first, last); + const char* first = path.data(); + const char* last = first + path.size(); + const char* stemFirst = FindFilename(first, last); const sizet filenameSize = last - stemFirst; // Check for "." and ".." filenames @@ -327,7 +327,7 @@ namespace p && !(stemFirst[0] == '.' && (filenameSize == 1 || (filenameSize == 2 && stemFirst[1] == '.')))) { - const TChar* stemLast = last; + const char* stemLast = last; while (stemLast > stemFirst) { --stemLast; @@ -350,23 +350,23 @@ namespace p StringView GetExtension(StringView path) { - const TChar* first = path.data(); - const TChar* last = first + path.size(); - const TChar* extension = FindExtension(first, last); + const char* first = path.data(); + const char* last = first + path.size(); + const char* extension = FindExtension(first, last); return StringView{extension, static_cast(last - extension)}; } void ReplaceExtension(String& path, StringView newExtension) { - const TChar* first = path.data(); - const TChar* last = first + path.size(); - const TChar* extension = FindExtension(first, last); + const char* first = path.data(); + const char* last = first + path.size(); + const char* extension = FindExtension(first, last); path.erase(extension - first, last - extension); if (!newExtension.empty()) { - if (newExtension[0] != dot) - path.push_back(dot); + if (newExtension[0] != details::dot) + path.push_back(details::dot); path.append(newExtension); } } @@ -488,9 +488,9 @@ namespace p return; } - const TChar* const otherEnd = other.data() + other.size(); - const TChar* otherRootNameEnd; - const TChar* otherRootEnd = FindRelativeChar(other.data(), otherEnd, otherRootNameEnd); + const char* const otherEnd = other.data() + other.size(); + const char* otherRootNameEnd; + const char* otherRootEnd = FindRelativeChar(other.data(), otherEnd, otherRootNameEnd); // Is absolute? if (otherRootEnd != other.data()) @@ -507,7 +507,7 @@ namespace p } else if (HasFilename(base)) { - base.push_back(preferredSeparator); + base.push_back(details::preferredSeparator); } } @@ -515,7 +515,7 @@ namespace p { if (!path.empty() && !IsElementSeparator(*(path.end() - 1))) { - path += preferredSeparator; + path += details::preferredSeparator; return true; } return false; @@ -629,7 +629,7 @@ namespace p String ToString(const Path& path) { - return path.string, std::allocator>(); + return path.string, std::allocator>(); } Path ToSTDPath(StringView pathStr) @@ -653,7 +653,7 @@ namespace p return {p, State::AtEnd}; } - const TChar* PathIterator::Peek() const noexcept + const char* PathIterator::Peek() const noexcept { auto tkEnd = GetNextTokenStartPos(); auto end = GetAfterBack(); @@ -662,8 +662,8 @@ namespace p void PathIterator::Increment() noexcept { - const TChar* const end = GetAfterBack(); - const TChar* const start = GetNextTokenStartPos(); + const char* const end = GetAfterBack(); + const char* const start = GetNextTokenStartPos(); if (start == end) { MakeState(State::AtEnd); @@ -673,7 +673,7 @@ namespace p switch (state) { case State::BeforeBegin: { - if (const TChar* tkEnd = ConsumeRootName(start, end)) + if (const char* tkEnd = ConsumeRootName(start, end)) { MakeState(State::InRootName, start, tkEnd); return; @@ -681,7 +681,7 @@ namespace p P_FALLTHROUGH; } case State::InRootName: { - const TChar* tkEnd = ConsumeAllSeparators(start, end); + const char* tkEnd = ConsumeAllSeparators(start, end); if (tkEnd) MakeState(State::InRootDir, start, tkEnd); else @@ -693,10 +693,10 @@ namespace p break; case State::InFilenames: { - const TChar* sepEnd = ConsumeAllSeparators(start, end); + const char* sepEnd = ConsumeAllSeparators(start, end); if (sepEnd != end) { - const TChar* tkEnd = ConsumeName(sepEnd, end); + const char* tkEnd = ConsumeName(sepEnd, end); if (tkEnd) { MakeState(State::InFilenames, sepEnd, tkEnd); @@ -714,8 +714,8 @@ namespace p void PathIterator::Decrement() noexcept { - const TChar* rEnd = GetBeforeFront(); - const TChar* rStart = GetCurrentTokenStartPos() - 1; + const char* rEnd = GetBeforeFront(); + const char* rStart = GetCurrentTokenStartPos() - 1; if (rStart == rEnd) // we're decrementing the begin { MakeState(State::BeforeBegin); @@ -726,14 +726,14 @@ namespace p { case State::AtEnd: { // Try to Consume a trailing separator or root directory first. - if (const TChar* sepEnd = ConsumeAllSeparators(rStart, rEnd)) + if (const char* sepEnd = ConsumeAllSeparators(rStart, rEnd)) { if (sepEnd == rEnd) { MakeState(State::InRootDir, path.data(), rStart + 1); return; } - const TChar* tkStart = ConsumeRootName(sepEnd, rEnd); + const char* tkStart = ConsumeRootName(sepEnd, rEnd); if (tkStart == rEnd) { MakeState(State::InRootDir, rStart, rStart + 1); @@ -745,7 +745,7 @@ namespace p } else { - const TChar* tkStart = ConsumeRootName(rStart, rEnd); + const char* tkStart = ConsumeRootName(rStart, rEnd); if (tkStart == rEnd) { MakeState(State::InRootName, tkStart + 1, rStart + 1); @@ -762,13 +762,13 @@ namespace p MakeState(State::InFilenames, ConsumeName(rStart, rEnd) + 1, rStart + 1); break; case State::InFilenames: { - const TChar* sepEnd = ConsumeAllSeparators(rStart, rEnd); + const char* sepEnd = ConsumeAllSeparators(rStart, rEnd); if (sepEnd == rEnd) { MakeState(State::InRootDir, path.data(), rStart + 1); break; } - const TChar* tkStart = ConsumeRootName(sepEnd ? sepEnd : rStart, rEnd); + const char* tkStart = ConsumeRootName(sepEnd ? sepEnd : rStart, rEnd); if (tkStart == rEnd) { if (sepEnd) @@ -866,7 +866,7 @@ namespace p return InRootName() || InRootDir(); } - void PathIterator::MakeState(State newState, const TChar* start, const TChar* end) noexcept + void PathIterator::MakeState(State newState, const char* start, const char* end) noexcept { state = newState; rawEntry = StringView(start, end - start); @@ -877,19 +877,19 @@ namespace p rawEntry = {}; } - const TChar* PathIterator::GetAfterBack() const noexcept + const char* PathIterator::GetAfterBack() const noexcept { return path.data() + path.size(); } - const TChar* PathIterator::GetBeforeFront() const noexcept + const char* PathIterator::GetBeforeFront() const noexcept { return path.data() - 1; } /// \brief Return a pointer to the first character after the currently /// lexed element. - const TChar* PathIterator::GetNextTokenStartPos() const noexcept + const char* PathIterator::GetNextTokenStartPos() const noexcept { switch (state) { @@ -905,7 +905,7 @@ namespace p /// \brief Return a pointer to the first character in the currently lexed /// element. - const TChar* PathIterator::GetCurrentTokenStartPos() const noexcept + const char* PathIterator::GetCurrentTokenStartPos() const noexcept { switch (state) { @@ -920,7 +920,7 @@ namespace p } // Consume all consecutive separators - const TChar* PathIterator::ConsumeAllSeparators(const TChar* p, const TChar* end) const noexcept + const char* PathIterator::ConsumeAllSeparators(const char* p, const char* end) const noexcept { if (p == nullptr || p == end || !IsSeparator(*p)) return nullptr; @@ -932,10 +932,10 @@ namespace p } // Consume exactly N separators, or return nullptr. - const TChar* PathIterator::ConsumeNSeparators( - const TChar* p, const TChar* end, int N) const noexcept + const char* PathIterator::ConsumeNSeparators( + const char* p, const char* end, int N) const noexcept { - const TChar* ret = ConsumeAllSeparators(p, end); + const char* ret = ConsumeAllSeparators(p, end); if (ret == nullptr) return nullptr; if (p < end) @@ -951,9 +951,9 @@ namespace p return nullptr; } - const TChar* PathIterator::ConsumeName(const TChar* p, const TChar* end) const noexcept + const char* PathIterator::ConsumeName(const char* p, const char* end) const noexcept { - const TChar* start = p; + const char* start = p; if (p == nullptr || p == end || IsSeparator(*p)) return nullptr; const int inc = p < end ? 1 : -1; @@ -965,14 +965,14 @@ namespace p // Iterating backwards and Consumed all the rest of the input. // Check if the start of the string would have been considered // a root name. - const TChar* rootEnd = ConsumeRootName(end + 1, start); + const char* rootEnd = ConsumeRootName(end + 1, start); if (rootEnd) return rootEnd - 1; } return p; } - const TChar* PathIterator::ConsumeDriveLetter(const TChar* p, const TChar* end) const noexcept + const char* PathIterator::ConsumeDriveLetter(const char* p, const char* end) const noexcept { if (p == end) return nullptr; @@ -990,7 +990,7 @@ namespace p } } - const TChar* PathIterator::ConsumeNetworkRoot(const TChar* p, const TChar* end) const noexcept + const char* PathIterator::ConsumeNetworkRoot(const char* p, const char* end) const noexcept { if (p == end) return nullptr; @@ -1000,12 +1000,12 @@ namespace p return ConsumeNSeparators(ConsumeName(p, end), end, 2); } - const TChar* PathIterator::ConsumeRootName(const TChar* p, const TChar* end) const noexcept + const char* PathIterator::ConsumeRootName(const char* p, const char* end) const noexcept { #if P_PLATFORM_WINDOWS - if (const TChar* ret = ConsumeDriveLetter(p, end)) + if (const char* ret = ConsumeDriveLetter(p, end)) return ret; - if (const TChar* ret = ConsumeNetworkRoot(p, end)) + if (const char* ret = ConsumeNetworkRoot(p, end)) return ret; #endif return nullptr; diff --git a/Src/Files/PlatformPaths.cpp b/Src/Files/PlatformPaths.cpp index 7b967277..bb9f2d63 100644 --- a/Src/Files/PlatformPaths.cpp +++ b/Src/Files/PlatformPaths.cpp @@ -158,12 +158,12 @@ namespace p char tempPath[MAX_PATH]; ZeroMemory(tempPath, sizeof(char) * MAX_PATH); - ::GetTempPath(MAX_PATH, tempPath); + ::GetTempPathA(MAX_PATH, tempPath); // Always expand the temp path in case windows returns short directory names. char fullTempPath[MAX_PATH]; ZeroMemory(fullTempPath, sizeof(char) * MAX_PATH); - ::GetLongPathName(tempPath, fullTempPath, MAX_PATH); + ::GetLongPathNameA(tempPath, fullTempPath, MAX_PATH); userTempPath = Strings::Convert(TStringView{fullTempPath}); std::replace(userTempPath.begin(), userTempPath.end(), '\\', '/'); @@ -221,11 +221,7 @@ namespace p bool WindowsPlatformPaths::SetCurrentPath(StringView path) { - #if PLATFORM_TCHAR_IS_WCHAR - return ::SetCurrentDirectoryW(path.data()); - #else return ::SetCurrentDirectoryA(path.data()); - #endif } diff --git a/Src/Memory/Alloc.cpp b/Src/Memory/Alloc.cpp deleted file mode 100644 index 4976ccc7..00000000 --- a/Src/Memory/Alloc.cpp +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved - -#include "Pipe/Memory/Alloc.h" - -#include "Pipe/Memory/Arena.h" -#include "Pipe/Memory/HeapArena.h" - -#include -#include - - -namespace p -{ - static Arena* currentArena = nullptr; - - void InitializeMemory() - { - GetHeapArena(); - } - - void* HeapAlloc(sizet size) - { - return malloc(size); - } - - void* HeapAlloc(sizet size, sizet align) - { -#if P_PLATFORM_WINDOWS - // TODO: Windows needs _aligned_free in order to use _aligned_alloc() - void* const ptr = malloc(size); -#else - void* const ptr = aligned_alloc(align, size); -#endif - return ptr; - } - - void* HeapRealloc(void* ptr, sizet size) - { - return realloc(ptr, size); - } - - void HeapFree(void* ptr) - { - free(ptr); - } - - - HeapArena& GetHeapArena() - { - static HeapArena heapArena{}; - return heapArena; - } - - Arena& GetCurrentArena() - { - return currentArena ? *currentArena : GetHeapArena(); - } - - void SetCurrentArena(Arena& arena) - { - currentArena = &arena; - } - - - void* Alloc(Arena& arena, sizet size) - { - return arena.Alloc(size); - } - - void* Alloc(Arena& arena, sizet size, sizet align) - { - return arena.Alloc(size, align); - } - - bool Realloc(Arena& arena, void* ptr, sizet ptrSize, sizet size) - { - return arena.Realloc(ptr, ptrSize, size); - } - - void Free(Arena& arena, void* ptr, sizet size) - { - arena.Free(ptr, size); - } - - void* Alloc(sizet size) - { - return Alloc(GetCurrentArena(), size); - } - - void* Alloc(sizet size, sizet align) - { - return Alloc(GetCurrentArena(), size, align); - } - - bool Realloc(void* ptr, sizet ptrSize, sizet size) - { - return Realloc(GetCurrentArena(), ptr, ptrSize, size); - } - - void Free(void* ptr, sizet size) - { - Free(GetCurrentArena(), ptr, size); - } -} // namespace p diff --git a/Src/Memory/Arena.cpp b/Src/Memory/Arena.cpp deleted file mode 100644 index 41a74997..00000000 --- a/Src/Memory/Arena.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved - -#include "Pipe/Memory/Arena.h" - -#include "Pipe/Memory/HeapArena.h" - - -namespace p -{ - ChildArena::ChildArena(Arena* inParent) : parent{inParent} - { - if (!parent) - { - parent = &GetCurrentArena(); - if (parent == this) [[unlikely]] - { - parent = &GetHeapArena(); - } - } - } -} // namespace p diff --git a/Src/Memory/BestFitArena.cpp b/Src/Memory/BestFitArena.cpp deleted file mode 100644 index a3900c06..00000000 --- a/Src/Memory/BestFitArena.cpp +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved - -#include "Pipe/Memory/BestFitArena.h" - -#include "Pipe/Core/Checks.h" -#include "Pipe/Core/Utility.h" -#include "Pipe/Memory/Alloc.h" -#include "Pipe/Memory/Memory.h" -#include "PipeAlgorithms.h" -#include "PipeMath.h" - - -namespace p -{ - bool operator==(const BestFitArena::Slot& a, sizet b) - { - return a.size == b; - } - bool operator<(const BestFitArena::Slot& a, sizet b) - { - return a.size < b; - } - bool operator>(const BestFitArena::Slot& a, sizet b) - { - return a.size > b; - } - bool operator<=(const BestFitArena::Slot& a, sizet b) - { - return a.size <= b; - } - bool operator>=(const BestFitArena::Slot& a, sizet b) - { - return a.size >= b; - } - bool operator==(sizet a, const BestFitArena::Slot& b) - { - return a == b.size; - } - bool operator<(sizet a, const BestFitArena::Slot& b) - { - return a < b.size; - } - bool operator>(sizet a, const BestFitArena::Slot& b) - { - return a > b.size; - } - bool operator<=(sizet a, const BestFitArena::Slot& b) - { - return a <= b.size; - } - bool operator>=(sizet a, const BestFitArena::Slot& b) - { - return a >= b.size; - } - - - BestFitArena::BestFitArena(Arena* parent, const sizet initialSize) : ChildArena(parent) - { - Interface(); - - P_Check(initialSize > 0); - block.data = GetParentArena().Alloc(initialSize); - block.size = initialSize; - // Set address at end of block. Size is 0 - // freeSlots.SetData(static_cast(block.GetData()) + block.Size()); - // Add first slot for the entire block - freeSlots.Add({reinterpret_cast(block.data), block.size}); - - freeSize = initialSize; - } - - BestFitArena::~BestFitArena() - { - p::Free(block.data, block.size); - block.data = nullptr; - } - - void* BestFitArena::Alloc(const sizet size) - { - const i32 slotIndex = FindSmallestSlot(size); - if (slotIndex == NO_INDEX || slotIndex >= freeSlots.Size()) [[unlikely]] - { - // Error("Couldn't fit {} bytes", size); - return nullptr; - } - - Slot& slot = freeSlots[slotIndex]; - u8* const start = slot.start; - u8* const end = start + size; - - ReduceSlot(slotIndex, slot, start, end); - freeSize -= size; - return start; - } - - void* BestFitArena::Alloc(const sizet size, sizet alignment) - { - // Maximum size needed, based on worst possible alignment: - const i32 slotIndex = FindSmallestSlot(size + (alignment - 1)); - if (slotIndex == NO_INDEX || slotIndex >= freeSlots.Size()) [[unlikely]] - { - // Error("Couldn't fit {} bytes", size); - return nullptr; - } - - Slot& slot = freeSlots[slotIndex]; - u8* const start = slot.start + GetAlignmentPadding(slot.start, alignment); - u8* const end = start + size; - - ReduceSlot(slotIndex, slot, start, end); - freeSize -= size; - return start; - } - - void BestFitArena::Free(void* ptr, sizet size) - { - if (ptr) - { - u8* const allocationStart = static_cast(ptr); - u8* const allocationEnd = allocationStart + size; - freeSize += allocationEnd - allocationStart; - AbsorbFreeSpace(allocationStart, allocationEnd); - } - } - - i32 BestFitArena::FindSmallestSlot(sizet neededSize) - { - if (pendingSort) [[unlikely]] - { - pendingSort = false; - if ((float(freeSlots.Size()) / freeSlots.Capacity()) < 0.1f) - { - // Dont shrink until there is 90% of unused space - freeSlots.Shrink(); - } - // Sort slots by size. Small first - freeSlots.Sort(TGreater<>()); - } - - // Find smallest slot fitting our required size - return freeSlots.FindSortedMin(neededSize, true); - // return freeSlots.UpperBound(neededSize); - } - - void BestFitArena::ReduceSlot( - i32 slotIndex, Slot& slot, u8* const allocationStart, u8* const allocationEnd) - { - if (allocationEnd == slot.End()) // Slot would become empty - { - if (allocationStart > slot.start) // Slot can still fill alignment gap - { - slot.size = allocationStart - slot.start; - pendingSort = true; - } - else - { - freeSlots.RemoveAtUnsafe(slotIndex, false); - } - return; - } - - u8* const oldSlotStart = slot.start; - slot.start = allocationEnd; - slot.size += oldSlotStart - allocationEnd; - - // If alignment leaves a gap in the slot, save this space as a new slot - if (allocationStart > oldSlotStart) - { - freeSlots.Add({oldSlotStart, sizet(allocationStart - oldSlotStart)}); - pendingSort = true; - } - // If slot is smaller than prev slot, we have to sort - else if (slotIndex > 0 && slot.size < freeSlots[slotIndex - 1].size) - { - pendingSort = true; - } - } - - void BestFitArena::AbsorbFreeSpace(u8* const allocationStart, u8* const allocationEnd) - { - // Find previous and/or next slots - i32 previousSlot = NO_INDEX; - i32 nextSlot = NO_INDEX; - for (i32 i = 0; i < freeSlots.Size(); ++i) - { - const Slot& slot = freeSlots[i]; - if (slot.start == allocationEnd) - { - nextSlot = i; - if (previousSlot != NO_INDEX) - { - break; // We found both slots - } - } - else if (slot.End() == allocationStart) - { - previousSlot = i; - if (nextSlot != NO_INDEX) - { - break; // We found both slots - } - } - } - - if (previousSlot != NO_INDEX && nextSlot != NO_INDEX) - { - // Expand next slot to the start of the previous slot - const Slot& next = freeSlots[nextSlot]; - Slot& previous = freeSlots[previousSlot]; - previous.size = next.End() - previous.start; - - freeSlots.RemoveAtSwapUnsafe(nextSlot); - } - else if (previousSlot != NO_INDEX) - { - Slot& previous = freeSlots[previousSlot]; - previous.size = allocationEnd - previous.start; - } - else if (nextSlot != NO_INDEX) - { - Slot& next = freeSlots[nextSlot]; - next.size += next.start - allocationStart; - next.start = allocationStart; - } - else - { - freeSlots.Add({allocationStart, sizet(allocationEnd - allocationStart)}); - } - pendingSort = true; - } -} // namespace p diff --git a/Src/Memory/BigBestFitArena.cpp b/Src/Memory/BigBestFitArena.cpp deleted file mode 100644 index 05cee13e..00000000 --- a/Src/Memory/BigBestFitArena.cpp +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved - -#include "Pipe/Memory/BigBestFitArena.h" - -#include "Pipe/Core/Checks.h" -#include "Pipe/Core/Utility.h" -#include "Pipe/Memory/Alloc.h" -#include "Pipe/Memory/Memory.h" -#include "PipeAlgorithms.h" -#include "PipeMath.h" - - -namespace p -{ - bool operator==(const BigBestFitArena::Slot& a, u32 b) - { - return a.size == b; - } - auto operator<=>(const BigBestFitArena::Slot& a, u32 b) - { - return a.size <=> b; - } - bool operator==(u32 a, const BigBestFitArena::Slot& b) - { - return a == b.size; - } - auto operator<=>(u32 a, const BigBestFitArena::Slot& b) - { - return a <=> b.size; - } - - BigBestFitArena::BigBestFitArena(Arena* parent, const sizet initialSize) : ChildArena(parent) - { - Interface(); - - P_Check(initialSize > 0); - block.data = GetParentArena().Alloc(initialSize); - block.size = initialSize; - // Set address at end of block. Size is 0 - // freeSlots.SetData(static_cast(block.GetData()) + block.Size()); - // Add first slot for the entire block - freeSlots.Add({0, u32(block.size)}); - - freeSize = initialSize; - } - - BigBestFitArena::~BigBestFitArena() - { - p::Free(block.data, block.size); - block.data = nullptr; - } - - void* BigBestFitArena::Alloc(const sizet size) - { - return Alloc(size, minAlignment); // Always align by header size - } - - void* BigBestFitArena::Alloc(const sizet size, sizet alignment) - { - // We always use at least 8 bytes of alignment for the header - alignment = Max(alignment, minAlignment); - - const i32 slotIndex = FindSmallestSlot(size + alignment - 1); - if (slotIndex == NO_INDEX || slotIndex >= freeSlots.Size()) - { - // Error("No slots can fit {} bytes!", size); - return nullptr; - } - - Slot& slot = freeSlots[slotIndex]; - - u8* slotStart = (u8*)block.data + slot.offset; - u8* const ptr = - slotStart - + GetAlignmentPaddingWithHeader(slotStart, alignment, sizeof(AllocationHeader)); - - auto* const header = GetHeader(ptr); - header->end = ptr + size; - header->end += GetAlignmentPadding(header->end, minAlignment); // Align end by 8 - - ReduceSlot( - slotIndex, slot, ToOffset(header, block.data), ToOffset(header->end, block.data)); - freeSize -= static_cast(header->end) - reinterpret_cast(header); - return ptr; - } - - void BigBestFitArena::Free(void* ptr, sizet size) - { - if (ptr) - { - auto* const header = GetHeader(ptr); - u8* const allocationStart = reinterpret_cast(header); - u8* const allocationEnd = header->end; - - freeSize += allocationEnd - allocationStart; - AbsorbFreeSpace( - ToOffset(allocationStart, block.data), ToOffset(allocationEnd, block.data)); - } - } - - i32 BigBestFitArena::FindSmallestSlot(sizet neededSize) - { - if (pendingSort) [[unlikely]] - { - pendingSort = false; - if (float(freeSlots.Size()) / freeSlots.Capacity() < 0.25f) - { - // Dont shrink until there is 75% of unused space - freeSlots.Shrink(); - } - - // Sort slots by size. Small first - freeSlots.Sort(TGreater<>()); - } - - // Find smallest slot fitting our required size - return freeSlots.FindSortedMin(neededSize, true); - } // namespace p - - void BigBestFitArena::ReduceSlot( - i32 slotIndex, Slot& slot, u32 allocationStart, u32 allocationEnd) - { - if (allocationEnd == slot.offset + slot.size) // Slot would become empty - { - if (allocationStart > slot.offset) // Slot can still fill alignment gap - { - slot.size = allocationStart - slot.offset; - pendingSort = true; - } - else - { - freeSlots.RemoveAtUnsafe(slotIndex, false); - } - return; - } - - u32 offset = slot.offset; - slot.offset = allocationEnd; - slot.size += offset - allocationEnd; - - // If alignment leaves a gap in the slot, save this space as a new slot - if (allocationStart > offset) - { - freeSlots.Add({offset, allocationStart - offset}); - } - // If slot is smaller than prev slot, we have to sort - else if (slotIndex > 0 && slot.size < freeSlots[slotIndex - 1].size) - { - pendingSort = true; - } - } - - void BigBestFitArena::AbsorbFreeSpace(u32 allocationStart, u32 allocationEnd) - { - // Find previous and/or next slots - i32 previousSlot = NO_INDEX; - i32 nextSlot = NO_INDEX; - for (i32 i = 0; i < freeSlots.Size(); ++i) - { - const Slot& slot = freeSlots[i]; - if (slot.offset == allocationEnd) - { - nextSlot = i; - if (previousSlot != NO_INDEX) - { - break; // We found both slots - } - } - else if ((slot.offset + slot.size) == allocationStart) - { - previousSlot = i; - if (nextSlot != NO_INDEX) - { - break; // We found both slots - } - } - } - - if (previousSlot != NO_INDEX && nextSlot != NO_INDEX) - { - // Expand next slot to the start of the previous slot - Slot& next = freeSlots[nextSlot]; - Slot& previous = freeSlots[previousSlot]; - const Slot combined{previous.offset, (next.offset - previous.offset) + next.size}; - // Remove the smallest slot, expand the other - if (next.size > previous.size) - { - next = combined; - freeSlots.RemoveAtSwapUnsafe(previousSlot); - } - else - { - previous = combined; - freeSlots.RemoveAtSwapUnsafe(nextSlot); - } - } - else if (previousSlot != NO_INDEX) - { - Slot& previous = freeSlots[previousSlot]; - previous.size = allocationEnd - previous.offset; - } - else if (nextSlot != NO_INDEX) - { - Slot& next = freeSlots[nextSlot]; - next.size += next.offset - allocationStart; - next.offset = allocationStart; - } - else - { - freeSlots.Add({allocationStart, allocationEnd - allocationStart}); - } - pendingSort = true; - } - - u32 BigBestFitArena::ToOffset(void* data, void* block) - { - return u32(static_cast(data) - static_cast(block)); - } -} // namespace p diff --git a/Src/Memory/Block.cpp b/Src/Memory/Block.cpp deleted file mode 100644 index 4ea5f0d7..00000000 --- a/Src/Memory/Block.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved - -#include "Pipe/Memory/Block.h" - -#include "Pipe/Core/Utility.h" - - -namespace p::Memory -{ - Block::Block(Block&& other) noexcept - { - data = Exchange(other.data, nullptr); - size = Exchange(other.size, 0); - } - Block& Block::operator=(Block&& other) noexcept - { - data = Exchange(other.data, nullptr); - size = Exchange(other.size, 0); - return *this; - } -} // namespace p::Memory diff --git a/Src/Memory/MemoryStats.cpp b/Src/Memory/MemoryStats.cpp index 6a0d91a2..b1af820c 100644 --- a/Src/Memory/MemoryStats.cpp +++ b/Src/Memory/MemoryStats.cpp @@ -2,7 +2,6 @@ #include "Pipe/Memory/MemoryStats.h" -#include "Pipe/Core/Backward.h" #include "Pipe/Core/String.h" #include "Pipe/Core/Utility.h" #include "PipeMath.h" @@ -14,7 +13,7 @@ namespace p { // Use a custom arena that doesn't track allocations. Otherwise tracking stats would loop - class PIPE_API MemoryStatsArena : public Arena + class P_API MemoryStatsArena : public Arena { public: MemoryStatsArena() @@ -48,21 +47,14 @@ namespace p } - MemoryStats::MemoryStats() - : allocations{GetStateArena()} -#if P_ENABLE_ALLOCATION_STACKS - , stacks{arena} -#endif - , freedAllocations{GetStateArena()} - {} + MemoryStats::MemoryStats() : allocations{GetStateArena()}, freedAllocations{GetStateArena()} {} MemoryStats::~MemoryStats() { Release(); } - void MemoryStats::PrintAllocationError( - StringView error, AllocationStats* allocation, const backward::StackTrace* stack) + void MemoryStats::PrintAllocationError(StringView error, AllocationStats* allocation) { String msg; Strings::FormatTo(msg, error); @@ -72,18 +64,6 @@ namespace p Strings::FormatTo(msg, " ({} {})", static_cast(allocation->ptr), Strings::ParseMemorySize(allocation->size)); } - - if (stack) - { - backward::TraceResolver tr; - tr.load_stacktrace(*stack); - for (sizet i = 0; i < stack->size(); ++i) - { - backward::ResolvedTrace trace = tr.resolve((*stack)[i]); - Strings::FormatTo(msg, "\n #{} {} {} [{}]", i, trace.object_filename, - trace.object_function, trace.addr); - } - } std::puts(msg.data()); } @@ -93,13 +73,6 @@ namespace p used += size; const AllocationStats item{static_cast(ptr), size}; i32 index = allocations.AddSorted(item, SortLessAllocationStats{}); - -#if P_ENABLE_ALLOCATION_STACKS - auto& stack = stacks.InsertRef(index); - backward::StackTrace stack{arena}; - stack.load_here(14 + 3); - stack.skip_n_firsts(3); -#endif } void MemoryStats::Remove(void* ptr, sizet size) @@ -116,12 +89,10 @@ namespace p if (index != NO_INDEX) { AllocationStats& allocation = allocations[index]; - P_CheckMsg(size == allocation.size, "Freed an allocation with a different size to which it got allocated with."); + P_CheckMsg(size == allocation.size, + "Freed an allocation with a different size to which it got allocated with."); freedAllocations.AddSorted(Move(allocation), SortLessAllocationStats{}); allocations.RemoveAt(index); -#if P_ENABLE_ALLOCATION_STACKS - stacks.RemoveAt(index); -#endif } else { @@ -140,17 +111,14 @@ namespace p { if (allocations.Size() > 0) { - TString errorMsg; + TString errorMsg; Strings::FormatTo( errorMsg, "{}: {} allocations were not freed!", name, allocations.Size()); const auto shown = Min(64, allocations.Size()); for (i32 i = 0; i < shown; ++i) { - PrintAllocationError("", &allocations[i], nullptr); -#if P_ENABLE_ALLOCATION_STACKS - PrintAllocationError("", &allocations[i], &stacks[i]); -#endif + PrintAllocationError("", &allocations[i]); } if (shown < allocations.Size()) @@ -161,9 +129,6 @@ namespace p std::puts(errorMsg.data()); } allocations.Clear(); -#if P_ENABLE_ALLOCATION_STACKS - stacks.Clear(); -#endif // P_ENABLE_ALLOCATION_STACKS used = 0; } } // namespace p diff --git a/Src/Memory/MonoLinearArena.cpp b/Src/Memory/MonoLinearArena.cpp deleted file mode 100644 index 1b0feb17..00000000 --- a/Src/Memory/MonoLinearArena.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved - -#include "Pipe/Memory/MonoLinearArena.h" - -#include "Pipe/Memory/Block.h" -#include "Pipe/Memory/Memory.h" - - -namespace p -{ - void* MonoLinearArena::Alloc(sizet size, sizet align) - { - u8* const allocEnd = (u8*)insert + size + GetAlignmentPadding(insert, align); - void* ptr; - // Not enough space in current block? - if (allocEnd <= block.End()) [[likely]] - { - insert = allocEnd; - ++count; - ptr = allocEnd - size; // Fast-path - } - else - { - // Allocation doesn't fit. Allocate in parent arena - ptr = GetParentArena().Alloc(size, align); - } - stats.Add(ptr, size); - return ptr; - } - - void MonoLinearArena::Free(void* ptr, sizet size) - { - stats.Remove(ptr, size); - if (ptr >= block.data && ptr < block.End()) [[likely]] - { - --count; - if (count <= 0) [[unlikely]] - { - Release(true); - } - } - else - { - GetParentArena().Free(ptr, size); - } - } - - void MonoLinearArena::Release(bool keepIfSelfAllocated) - { - stats.Release(); - insert = block.data; - count = 0; - if (selfAllocated && !keepIfSelfAllocated) - { - // Self allcoated block gets freed - GetParentArena().Free(block.data, block.size); - block = {}; - } - } -} // namespace p diff --git a/Src/Memory/MultiLinearArena.cpp b/Src/Memory/MultiLinearArena.cpp deleted file mode 100644 index 947c68ed..00000000 --- a/Src/Memory/MultiLinearArena.cpp +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved - -#include "Pipe/Memory/MultiLinearArena.h" - -#include "Pipe/Memory/Block.h" -#include "Pipe/Memory/Memory.h" - - -namespace p -{ - template - void LinearBasePool::AllocateBlock(Arena& parentArena) - { - LinearBlock* const lastBlock = freeBlock; - - // Allocate aligning by blockSize - void* ptr = parentArena.Alloc(blockSize + sizeof(LinearBlock), GetBlockSize()); - - void* blockPtr = (u8*)ptr + GetAlignmentPadding(ptr, GetBlockSize()); - freeBlock = new (blockPtr) LinearBlock(); - insert = freeBlock + 1; - freeBlock->count = 1; // Count +1 prevents Free cleaning blocks being filled - freeBlock->last = lastBlock; - freeBlock->unaligned = ptr; - - if (lastBlock) - { - lastBlock->next = freeBlock; - // Dismark block allowing freeing it: - --lastBlock->count; - // In theory, we would need to free lastBlock if full here... - // But we only reach AllocateBlock if the previous one is full! - } - } - - template - void LinearBasePool::FreeBlock(Arena& parentArena, LinearBlock* block) - { - P_Check(block); - // freeBlock is never freed. No need to handle it. - if (block->next) - { - block->next->last = block->last; - } - if (block->last) - { - block->last->next = block->next; - } - parentArena.Free(block->unaligned, GetAllocatedBlockSize()); - } - - template - void* LinearBasePool::Alloc(Arena& parentArena, sizet size, sizet align) - { - insert = (u8*)insert + size + GetAlignmentPadding(insert, align); - - // Not enough space in current block? - if (freeBlock && insert <= GetBlockEnd(freeBlock)) [[likely]] - { - ++freeBlock->count; - return (u8*)insert - size; // Fast-path - } - - AllocateBlock(parentArena); - // Recalculate allocation with new block - insert = (u8*)insert + size + GetAlignmentPadding(insert, align); - ++freeBlock->count; - return (u8*)insert - size; - } - - template - void LinearBasePool::Free(Arena& parentArena, void* ptr, sizet size) - { - auto* block = static_cast(GetAlignedBlock(ptr, GetBlockSize())); - if (!block) [[unlikely]] - { - return; - } - - --block->count; - if (block->count <= 0) [[unlikely]] - { - // If the block is empty and was marked full, free it - FreeBlock(parentArena, block); - } - } - - template - void LinearBasePool::Release(Arena& parentArena) - { - // Iterate backwards all blocks while freeing them - insert = nullptr; - while (freeBlock != nullptr) - { - LinearBlock* const block = freeBlock; - freeBlock = freeBlock->last; - parentArena.Free(block->unaligned, GetAllocatedBlockSize()); - } - freeBlock = nullptr; - } - - void* MultiLinearArena::Alloc(sizet size, sizet align) - { - if (size < smallPool.maxSize) - { - return smallPool.Alloc(GetParentArena(), size, align); - } - else if (size < mediumPool.maxSize) - { - return mediumPool.Alloc(GetParentArena(), size, align); - } - else if (size < bigPool.maxSize) - { - return bigPool.Alloc(GetParentArena(), size, align); - } - else - { - return GetParentArena().Alloc(size, align); - } - } - - void MultiLinearArena::Free(void* ptr, sizet size) - { - if (size < smallPool.maxSize) - { - smallPool.Free(GetParentArena(), ptr, size); - } - else if (size < mediumPool.maxSize) - { - mediumPool.Free(GetParentArena(), ptr, size); - } - else if (size < bigPool.maxSize) - { - bigPool.Free(GetParentArena(), ptr, size); - } - else - { - GetParentArena().Free(ptr, size); - } - } - - void MultiLinearArena::Release() - { - smallPool.Release(GetParentArena()); - mediumPool.Release(GetParentArena()); - bigPool.Release(GetParentArena()); - } -} // namespace p diff --git a/Src/Memory/OwnPtr.cpp b/Src/Memory/OwnPtr.cpp index b39d8b23..c38857c1 100644 --- a/Src/Memory/OwnPtr.cpp +++ b/Src/Memory/OwnPtr.cpp @@ -22,8 +22,8 @@ namespace p // With this weaks know the value has been deleted counter->deleter = nullptr; } - counter = nullptr; value = nullptr; + counter = nullptr; } void Ptr::Reset() @@ -69,16 +69,18 @@ namespace p { value = other.value; counter = other.counter; + other.value = nullptr; other.counter = nullptr; } - void Ptr::MoveFrom(Ptr&& other) + void Ptr::MoveFromUnsafe(Ptr&& other) { if (counter != other.counter) { Reset(); value = other.value; counter = other.counter; + other.value = nullptr; other.counter = nullptr; } else // If equals, we reset previous anyway @@ -87,7 +89,7 @@ namespace p } } - void Ptr::CopyFrom(const Ptr& other) + void Ptr::CopyFromUnsafe(const Ptr& other) { if (counter != other.counter) { @@ -112,7 +114,7 @@ namespace p { --counter->weakCount; } - counter = nullptr; value = nullptr; + counter = nullptr; } } // namespace p diff --git a/Src/Memory/STLAllocator.cpp b/Src/Memory/STLAllocator.cpp deleted file mode 100644 index c87a5763..00000000 --- a/Src/Memory/STLAllocator.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright 2015-2024 Piperift - All rights reserved - -#include "Pipe/Memory/STLAllocator.h" diff --git a/Src/Pipe.cpp b/Src/Pipe.cpp index 183d4e01..b29e6da3 100644 --- a/Src/Pipe.cpp +++ b/Src/Pipe.cpp @@ -1,22 +1,23 @@ // Copyright 2015-2024 Piperift - All rights reserved -#include "Pipe/Memory/NewDelete.h" -// Override as first include +// #include "PipeNewDelete.h" +// Override as first include #include "Pipe.h" + #include "Pipe/Core/Log.h" #include "Pipe/Files/Paths.h" -#include "Pipe/Memory/Alloc.h" +#include "PipeMemory.h" #include "PipeReflect.h" namespace p { - void Initialize(StringView logPath) + void Initialize(Logger* logger) { InitializeMemory(); InitializeReflect(); - InitLog(logPath); + InitLog(logger); } void Shutdown() diff --git a/Src/Pipe/Extern/README.md b/Src/Pipe/Extern/README.md new file mode 100644 index 00000000..d72188ae --- /dev/null +++ b/Src/Pipe/Extern/README.md @@ -0,0 +1,8 @@ +# External Dependencies + +External dependencies that get compiled directly into Pipe will be in this folder. + +Their code will be unchanged except for includes to same library headers. + +Most of these dependencies as well, are compiled privately to pipe and won't be included from Pipe headers. + diff --git a/Src/Pipe/Extern/portable-file-dialogs.h b/Src/Pipe/Extern/portable-file-dialogs.h new file mode 100644 index 00000000..5b9861ca --- /dev/null +++ b/Src/Pipe/Extern/portable-file-dialogs.h @@ -0,0 +1,1923 @@ +// +// Portable File Dialogs +// +// Copyright © 2018–2022 Sam Hocevar +// +// This library is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#pragma once + +#if _WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN 1 + #endif + #include + #include + #include + #include // IFileDialog + #include + #include // GetUserProfileDirectory() + #include + + #include // std::async + + +#elif __EMSCRIPTEN__ + #include + +#else + #ifndef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 2 // for popen() + #endif + #ifdef __APPLE__ + #ifndef _DARWIN_C_SOURCE + #define _DARWIN_C_SOURCE + #endif + #endif + #include // fcntl() + #include // getpwnam() + #include // stat() + #include // waitpid() + #include // read(), pipe(), dup2(), getuid() + + #include // ::kill, std::signal + #include // popen() + #include // std::getenv() + +#endif + +#include // std::chrono +#include // std::ostream +#include // std::map +#include // std::shared_ptr +#include // std::regex +#include // std::set +#include // std::string +#include // std::mutex, std::this_thread + + +// Versions of mingw64 g++ up to 9.3.0 do not have a complete IFileDialog +#ifndef PFD_HAS_IFILEDIALOG + #define PFD_HAS_IFILEDIALOG 1 + #if (defined __MINGW64__ || defined __MINGW32__) && defined __GXX_ABI_VERSION + #if __GXX_ABI_VERSION <= 1013 + #undef PFD_HAS_IFILEDIALOG + #define PFD_HAS_IFILEDIALOG 0 + #endif + #endif +#endif + +namespace pfd +{ + + enum class button + { + cancel = -1, + ok, + yes, + no, + abort, + retry, + ignore, + }; + + enum class choice + { + ok = 0, + ok_cancel, + yes_no, + yes_no_cancel, + retry_cancel, + abort_retry_ignore, + }; + + enum class icon + { + info = 0, + warning, + error, + question, + }; + + // Additional option flags for various dialog constructors + enum class opt : uint8_t + { + none = 0, + // For file open, allow multiselect. + multiselect = 0x1, + // For file save, force overwrite and disable the confirmation dialog. + force_overwrite = 0x2, + // For folder select, force path to be the provided argument instead + // of the last opened directory, which is the Microsoft-recommended, + // user-friendly behaviour. + force_path = 0x4, + }; + + inline opt operator|(opt a, opt b) + { + return opt(uint8_t(a) | uint8_t(b)); + } + inline bool operator&(opt a, opt b) + { + return bool(uint8_t(a) & uint8_t(b)); + } + + // The settings class, only exposing to the user a way to set verbose mode + // and to force a rescan of installed desktop helpers (zenity, kdialog…). + class settings + { + public: + static bool available(); + + static void verbose(bool value); + static void rescan(); + + protected: + explicit settings(bool resync = false); + + bool check_program(std::string const& program); + + inline bool is_osascript() const; + inline bool is_zenity() const; + inline bool is_kdialog() const; + + enum class flag + { + is_scanned = 0, + is_verbose, + + has_zenity, + has_matedialog, + has_qarma, + has_kdialog, + is_vista, + + max_flag, + }; + + // Static array of flags for internal state + bool const& flags(flag in_flag) const; + + // Non-const getter for the static array of flags + bool& flags(flag in_flag); + }; + + // Internal classes, not to be used by client applications + namespace internal + { + + // Process wait timeout, in milliseconds + static int const default_wait_timeout = 20; + + class executor + { + friend class dialog; + + public: + // High level function to get the result of a command + std::string result(int* exit_code = nullptr); + + // High level function to abort + bool kill(); + +#if _WIN32 + void start_func(std::function const& fun); + static BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam); +#elif __EMSCRIPTEN__ + void start(int exit_code); +#else + void start_process(std::vector const& command); +#endif + + ~executor(); + + protected: + bool ready(int timeout = default_wait_timeout); + void stop(); + + private: + bool m_running = false; + std::string m_stdout; + int m_exit_code = -1; +#if _WIN32 + std::future m_future; + std::set m_windows; + std::condition_variable m_cond; + std::mutex m_mutex; + DWORD m_tid; +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something +#else + pid_t m_pid = 0; + int m_fd = -1; +#endif + }; + + class platform + { + protected: +#if _WIN32 + // Helper class around LoadLibraryA() and GetProcAddress() with some safety + class dll + { + public: + dll(std::string const& name); + ~dll(); + + template + class proc + { + public: + proc(dll const& lib, std::string const& sym) + : m_proc( + reinterpret_cast((void*)::GetProcAddress(lib.handle, sym.c_str()))) + {} + + operator bool() const + { + return m_proc != nullptr; + } + operator T*() const + { + return m_proc; + } + + private: + T* m_proc; + }; + + private: + HMODULE handle; + }; + + // Helper class around CoInitialize() and CoUnInitialize() + class ole32_dll : public dll + { + public: + ole32_dll(); + ~ole32_dll(); + bool is_initialized(); + + private: + HRESULT m_state; + }; + + // Helper class around CreateActCtx() and ActivateActCtx() + class new_style_context + { + public: + new_style_context(); + ~new_style_context(); + + private: + HANDLE create(); + ULONG_PTR m_cookie = 0; + }; +#endif + }; + + class dialog : protected settings, + protected platform + { + public: + bool ready(int timeout = default_wait_timeout) const; + bool kill() const; + + protected: + explicit dialog(); + + std::vector desktop_helper() const; + static std::string buttons_to_name(choice _choice); + static std::string get_icon_name(icon _icon); + + std::string powershell_quote(std::string const& str) const; + std::string osascript_quote(std::string const& str) const; + std::string shell_quote(std::string const& str) const; + + // Keep handle to executing command + std::shared_ptr m_async; + }; + + class file_dialog : public dialog + { + protected: + enum type + { + open, + save, + folder, + }; + + file_dialog(type in_type, std::string const& title, + std::string const& default_path = "", std::vector const& filters = {}, + opt options = opt::none); + + protected: + std::string string_result(); + std::vector vector_result(); + +#if _WIN32 + static int CALLBACK bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData); + #if PFD_HAS_IFILEDIALOG + std::string select_folder_vista(IFileDialog* ifd, bool force_path); + #endif + + std::wstring m_wtitle; + std::wstring m_wdefault_path; + + std::vector m_vector_result; +#endif + }; + + } // namespace internal + + // + // The path class provides some platform-specific path constants + // + + class path : protected internal::platform + { + public: + static std::string home(); + static std::string separator(); + }; + + // + // The notify widget + // + + class notify : public internal::dialog + { + public: + notify(std::string const& title, std::string const& message, icon _icon = icon::info); + }; + + // + // The message widget + // + + class message : public internal::dialog + { + public: + message(std::string const& title, std::string const& text, + choice _choice = choice::ok_cancel, icon _icon = icon::info); + + button result(); + + private: + // Some extra logic to map the exit code to button number + std::map m_mappings; + }; + + // + // The open_file, save_file, and open_folder widgets + // + + class open_file : public internal::file_dialog + { + public: + open_file(std::string const& title, std::string const& default_path = "", + std::vector const& filters = {"All Files", "*"}, opt options = opt::none); + +#if defined(__has_cpp_attribute) + #if __has_cpp_attribute(deprecated) + // Backwards compatibility + [[deprecated("Use pfd::opt::multiselect instead of allow_multiselect")]] + #endif +#endif + open_file(std::string const& title, std::string const& default_path, + std::vector const& filters, bool allow_multiselect); + + std::vector result(); + }; + + class save_file : public internal::file_dialog + { + public: + save_file(std::string const& title, std::string const& default_path = "", + std::vector const& filters = {"All Files", "*"}, opt options = opt::none); + +#if defined(__has_cpp_attribute) + #if __has_cpp_attribute(deprecated) + // Backwards compatibility + [[deprecated("Use pfd::opt::force_overwrite instead of confirm_overwrite")]] + #endif +#endif + save_file(std::string const& title, std::string const& default_path, + std::vector const& filters, bool confirm_overwrite); + + std::string result(); + }; + + class select_folder : public internal::file_dialog + { + public: + select_folder(std::string const& title, std::string const& default_path = "", + opt options = opt::none); + + std::string result(); + }; + + // + // Below this are all the method implementations. You may choose to define the + // macro PFD_SKIP_IMPLEMENTATION everywhere before including this header except + // in one place. This may reduce compilation times. + // + +#if !defined PFD_SKIP_IMPLEMENTATION + + // internal free functions implementations + + namespace internal + { + + #if _WIN32 + static inline std::wstring str2wstr(std::string const& str) + { + int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0); + std::wstring ret(len, '\0'); + MultiByteToWideChar( + CP_UTF8, 0, str.c_str(), (int)str.size(), (LPWSTR)ret.data(), (int)ret.size()); + return ret; + } + + static inline std::string wstr2str(std::wstring const& str) + { + int len = WideCharToMultiByte( + CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0, nullptr, nullptr); + std::string ret(len, '\0'); + WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPSTR)ret.data(), + (int)ret.size(), nullptr, nullptr); + return ret; + } + + static inline bool is_vista() + { + OSVERSIONINFOEXW osvi; + memset(&osvi, 0, sizeof(osvi)); + DWORDLONG const mask = VerSetConditionMask( + VerSetConditionMask(VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + osvi.dwOSVersionInfoSize = sizeof(osvi); + osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA); + osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA); + osvi.wServicePackMajor = 0; + + return VerifyVersionInfoW( + &osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, mask) + != FALSE; + } + #endif + + // This is necessary until C++20 which will have std::string::ends_with() etc. + + static inline bool ends_with(std::string const& str, std::string const& suffix) + { + return suffix.size() <= str.size() + && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; + } + + static inline bool starts_with(std::string const& str, std::string const& prefix) + { + return prefix.size() <= str.size() && str.compare(0, prefix.size(), prefix) == 0; + } + + // This is necessary until C++17 which will have std::filesystem::is_directory + + static inline bool is_directory(std::string const& path) + { + #if _WIN32 + auto attr = GetFileAttributesA(path.c_str()); + return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY); + #elif __EMSCRIPTEN__ + // TODO + return false; + #else + struct stat s; + return stat(path.c_str(), &s) == 0 && S_ISDIR(s.st_mode); + #endif + } + + // This is necessary because getenv is not thread-safe + + static inline std::string getenv(std::string const& str) + { + #if _MSC_VER + char* buf = nullptr; + size_t size = 0; + if (_dupenv_s(&buf, &size, str.c_str()) == 0 && buf) + { + std::string ret(buf); + free(buf); + return ret; + } + return ""; + #else + auto buf = std::getenv(str.c_str()); + return buf ? buf : ""; + #endif + } + + } // namespace internal + + // settings implementation + + inline settings::settings(bool resync) + { + flags(flag::is_scanned) &= !resync; + + if (flags(flag::is_scanned)) + return; + + auto pfd_verbose = internal::getenv("PFD_VERBOSE"); + auto match_no = std::regex("(|0|no|false)", std::regex_constants::icase); + if (!std::regex_match(pfd_verbose, match_no)) + flags(flag::is_verbose) = true; + + #if _WIN32 + flags(flag::is_vista) = internal::is_vista(); + #elif !__APPLE__ + flags(flag::has_zenity) = check_program("zenity"); + flags(flag::has_matedialog) = check_program("matedialog"); + flags(flag::has_qarma) = check_program("qarma"); + flags(flag::has_kdialog) = check_program("kdialog"); + + // If multiple helpers are available, try to default to the best one + if (flags(flag::has_zenity) && flags(flag::has_kdialog)) + { + auto desktop_name = internal::getenv("XDG_SESSION_DESKTOP"); + if (desktop_name == std::string("gnome")) + flags(flag::has_kdialog) = false; + else if (desktop_name == std::string("KDE")) + flags(flag::has_zenity) = false; + } + #endif + + flags(flag::is_scanned) = true; + } + + inline bool settings::available() + { + #if _WIN32 + return true; + #elif __APPLE__ + return true; + #elif __EMSCRIPTEN__ + // FIXME: Return true after implementation is complete. + return false; + #else + settings tmp; + return tmp.flags(flag::has_zenity) || tmp.flags(flag::has_matedialog) + || tmp.flags(flag::has_qarma) || tmp.flags(flag::has_kdialog); + #endif + } + + inline void settings::verbose(bool value) + { + settings().flags(flag::is_verbose) = value; + } + + inline void settings::rescan() + { + settings(/* resync = */ true); + } + + // Check whether a program is present using “which”. + inline bool settings::check_program(std::string const& program) + { + #if _WIN32 + (void)program; + return false; + #elif __EMSCRIPTEN__ + (void)program; + return false; + #else + int exit_code = -1; + internal::executor async; + async.start_process({"/bin/sh", "-c", "which " + program}); + async.result(&exit_code); + return exit_code == 0; + #endif + } + + inline bool settings::is_osascript() const + { + #ifdef __APPLE__ + return true; + #else + return false; + #endif + } + + inline bool settings::is_zenity() const + { + return flags(flag::has_zenity) || flags(flag::has_matedialog) || flags(flag::has_qarma); + } + + inline bool settings::is_kdialog() const + { + return flags(flag::has_kdialog); + } + + inline bool const& settings::flags(flag in_flag) const + { + static bool flags[size_t(flag::max_flag)]; + return flags[size_t(in_flag)]; + } + + inline bool& settings::flags(flag in_flag) + { + return const_cast(static_cast(this)->flags(in_flag)); + } + + // path implementation + inline std::string path::home() + { + #if _WIN32 + // First try the USERPROFILE environment variable + auto user_profile = internal::getenv("USERPROFILE"); + if (user_profile.size() > 0) + return user_profile; + // Otherwise, try GetUserProfileDirectory() + HANDLE token = nullptr; + DWORD len = MAX_PATH; + char buf[MAX_PATH] = {'\0'}; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) + { + dll userenv("userenv.dll"); + dll::proc get_user_profile_directory( + userenv, "GetUserProfileDirectoryA"); + get_user_profile_directory(token, buf, &len); + CloseHandle(token); + if (*buf) + return buf; + } + #elif __EMSCRIPTEN__ + return "/"; + #else + // First try the HOME environment variable + auto home = internal::getenv("HOME"); + if (home.size() > 0) + return home; + // Otherwise, try getpwuid_r() + size_t len = 4096; + #if defined(_SC_GETPW_R_SIZE_MAX) + auto size_max = sysconf(_SC_GETPW_R_SIZE_MAX); + if (size_max != -1) + len = size_t(size_max); + #endif + std::vector buf(len); + struct passwd pwd, *result; + if (getpwuid_r(getuid(), &pwd, buf.data(), buf.size(), &result) == 0) + return result->pw_dir; + #endif + return "/"; + } + + inline std::string path::separator() + { + #if _WIN32 + return "\\"; + #else + return "/"; + #endif + } + + // executor implementation + + inline std::string internal::executor::result(int* exit_code /* = nullptr */) + { + stop(); + if (exit_code) + *exit_code = m_exit_code; + return m_stdout; + } + + inline bool internal::executor::kill() + { + #if _WIN32 + if (m_future.valid()) + { + // Close all windows that weren’t open when we started the future + auto previous_windows = m_windows; + EnumWindows(&enum_windows_callback, (LPARAM)this); + for (auto hwnd : m_windows) + if (previous_windows.find(hwnd) == previous_windows.end()) + { + SendMessage(hwnd, WM_CLOSE, 0, 0); + // Also send IDNO in case of a Yes/No or Abort/Retry/Ignore messagebox + SendMessage(hwnd, WM_COMMAND, IDNO, 0); + } + } + #elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something + return false; // cannot kill + #else + ::kill(m_pid, SIGKILL); + #endif + stop(); + return true; + } + + #if _WIN32 + inline BOOL CALLBACK internal::executor::enum_windows_callback(HWND hwnd, LPARAM lParam) + { + auto that = (executor*)lParam; + + DWORD pid; + auto tid = GetWindowThreadProcessId(hwnd, &pid); + if (tid == that->m_tid) + that->m_windows.insert(hwnd); + return TRUE; + } + #endif + + #if _WIN32 + inline void internal::executor::start_func(std::function const& fun) + { + stop(); + + auto trampoline = [fun, this]() { + // Save our thread id so that the caller can cancel us + m_tid = GetCurrentThreadId(); + EnumWindows(&enum_windows_callback, (LPARAM)this); + m_cond.notify_all(); + return fun(&m_exit_code); + }; + + std::unique_lock lock(m_mutex); + m_future = std::async(std::launch::async, trampoline); + m_cond.wait(lock); + m_running = true; + } + + #elif __EMSCRIPTEN__ + inline void internal::executor::start(int exit_code) + { + m_exit_code = exit_code; + } + + #else + inline void internal::executor::start_process(std::vector const& command) + { + stop(); + m_stdout.clear(); + m_exit_code = -1; + + int in[2], out[2]; + if (pipe(in) != 0 || pipe(out) != 0) + return; + + m_pid = fork(); + if (m_pid < 0) + return; + + close(in[m_pid ? 0 : 1]); + close(out[m_pid ? 1 : 0]); + + if (m_pid == 0) + { + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + + // Ignore stderr so that it doesn’t pollute the console (e.g. GTK+ errors from zenity) + int fd = open("/dev/null", O_WRONLY); + dup2(fd, STDERR_FILENO); + close(fd); + + std::vector args; + std::transform(command.cbegin(), command.cend(), std::back_inserter(args), + [](std::string const& s) { + return const_cast(s.c_str()); + }); + args.push_back(nullptr); // null-terminate argv[] + + execvp(args[0], args.data()); + exit(1); + } + + close(in[1]); + m_fd = out[0]; + auto flags = fcntl(m_fd, F_GETFL); + fcntl(m_fd, F_SETFL, flags | O_NONBLOCK); + + m_running = true; + } + #endif + + inline internal::executor::~executor() + { + stop(); + } + + inline bool internal::executor::ready(int timeout /* = default_wait_timeout */) + { + if (!m_running) + return true; + + #if _WIN32 + if (m_future.valid()) + { + auto status = m_future.wait_for(std::chrono::milliseconds(timeout)); + if (status != std::future_status::ready) + { + // On Windows, we need to run the message pump. If the async + // thread uses a Windows API dialog, it may be attached to the + // main thread and waiting for messages that only we can dispatch. + MSG msg; + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return false; + } + + m_stdout = m_future.get(); + } + #elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something + (void)timeout; + #else + char buf[BUFSIZ]; + ssize_t received = read(m_fd, buf, BUFSIZ); // Flawfinder: ignore + if (received > 0) + { + m_stdout += std::string(buf, received); + return false; + } + + // Reap child process if it is dead. It is possible that the system has already reaped it + // (this happens when the calling application handles or ignores SIG_CHLD) and results in + // waitpid() failing with ECHILD. Otherwise we assume the child is running and we sleep for + // a little while. + int status; + pid_t child = waitpid(m_pid, &status, WNOHANG); + if (child != m_pid && (child >= 0 || errno != ECHILD)) + { + // FIXME: this happens almost always at first iteration + std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); + return false; + } + + close(m_fd); + m_exit_code = WEXITSTATUS(status); + #endif + + m_running = false; + return true; + } + + inline void internal::executor::stop() + { + // Loop until the user closes the dialog + while (!ready()) + ; + } + + // dll implementation + + #if _WIN32 + inline internal::platform::dll::dll(std::string const& name) + : handle(::LoadLibraryA(name.c_str())) + {} + + inline internal::platform::dll::~dll() + { + if (handle) + ::FreeLibrary(handle); + } + #endif // _WIN32 + + // ole32_dll implementation + + #if _WIN32 + inline internal::platform::ole32_dll::ole32_dll() : dll("ole32.dll") + { + // Use COINIT_MULTITHREADED because COINIT_APARTMENTTHREADED causes crashes. + // See https://github.com/samhocevar/portable-file-dialogs/issues/51 + auto coinit = proc(*this, "CoInitializeEx"); + m_state = coinit(nullptr, COINIT_MULTITHREADED); + } + + inline internal::platform::ole32_dll::~ole32_dll() + { + if (is_initialized()) + proc(*this, "CoUninitialize")(); + } + + inline bool internal::platform::ole32_dll::is_initialized() + { + return m_state == S_OK || m_state == S_FALSE; + } + #endif + + // new_style_context implementation + + #if _WIN32 + inline internal::platform::new_style_context::new_style_context() + { + // Only create one activation context for the whole app lifetime. + static HANDLE hctx = create(); + + if (hctx != INVALID_HANDLE_VALUE) + ActivateActCtx(hctx, &m_cookie); + } + + inline internal::platform::new_style_context::~new_style_context() + { + DeactivateActCtx(0, m_cookie); + } + + inline HANDLE internal::platform::new_style_context::create() + { + // This “hack” seems to be necessary for this code to work on windows XP. + // Without it, dialogs do not show and close immediately. GetError() + // returns 0 so I don’t know what causes this. I was not able to reproduce + // this behavior on Windows 7 and 10 but just in case, let it be here for + // those versions too. + // This hack is not required if other dialogs are used (they load comdlg32 + // automatically), only if message boxes are used. + dll comdlg32("comdlg32.dll"); + + // Using approach as shown here: https://stackoverflow.com/a/10444161 + UINT len = ::GetSystemDirectoryA(nullptr, 0); + std::string sys_dir(len, '\0'); + ::GetSystemDirectoryA(&sys_dir[0], len); + + ACTCTXA act_ctx = { + // Do not set flag ACTCTX_FLAG_SET_PROCESS_DEFAULT, since it causes a + // crash with error “default context is already set”. + sizeof(act_ctx), + ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID, + "shell32.dll", + 0, + 0, + sys_dir.c_str(), + (LPCSTR)124, + nullptr, + 0, + }; + + return ::CreateActCtxA(&act_ctx); + } + #endif // _WIN32 + + // dialog implementation + + inline bool internal::dialog::ready(int timeout /* = default_wait_timeout */) const + { + return m_async->ready(timeout); + } + + inline bool internal::dialog::kill() const + { + return m_async->kill(); + } + + inline internal::dialog::dialog() : m_async(std::make_shared()) {} + + inline std::vector internal::dialog::desktop_helper() const + { + #ifdef __APPLE__ + return {"osascript"}; + #else + return {flags(flag::has_zenity) ? "zenity" + : flags(flag::has_matedialog) ? "matedialog" + : flags(flag::has_qarma) ? "qarma" + : flags(flag::has_kdialog) ? "kdialog" + : "echo"}; + #endif + } + + inline std::string internal::dialog::buttons_to_name(choice _choice) + { + switch (_choice) + { + case choice::ok_cancel: return "okcancel"; + case choice::yes_no: return "yesno"; + case choice::yes_no_cancel: return "yesnocancel"; + case choice::retry_cancel: return "retrycancel"; + case choice::abort_retry_ignore: + return "abortretryignore"; + /* case choice::ok: */ default: + return "ok"; + } + } + + inline std::string internal::dialog::get_icon_name(icon _icon) + { + switch (_icon) + { + case icon::warning: return "warning"; + case icon::error: return "error"; + case icon::question: return "question"; + // Zenity wants "information" but WinForms wants "info" + /* case icon::info: */ default: + #if _WIN32 + return "info"; + #else + return "information"; + #endif + } + } + + // This is only used for debugging purposes + inline std::ostream& operator<<(std::ostream& s, std::vector const& v) + { + int not_first = 0; + for (auto& e : v) + s << (not_first++ ? " " : "") << e; + return s; + } + + // Properly quote a string for Powershell: replace ' or " with '' or "" + // FIXME: we should probably get rid of newlines! + // FIXME: the \" sequence seems unsafe, too! + // XXX: this is no longer used but I would like to keep it around just in case + inline std::string internal::dialog::powershell_quote(std::string const& str) const + { + return "'" + std::regex_replace(str, std::regex("['\"]"), "$&$&") + "'"; + } + + // Properly quote a string for osascript: replace \ or " with \\ or \" + // XXX: this also used to replace ' with \' when popen was used, but it would be + // smarter to do shell_quote(osascript_quote(...)) if this is needed again. + inline std::string internal::dialog::osascript_quote(std::string const& str) const + { + return "\"" + std::regex_replace(str, std::regex("[\\\\\"]"), "\\$&") + "\""; + } + + // Properly quote a string for the shell: just replace ' with '\'' + // XXX: this is no longer used but I would like to keep it around just in case + inline std::string internal::dialog::shell_quote(std::string const& str) const + { + return "'" + std::regex_replace(str, std::regex("'"), "'\\''") + "'"; + } + + // file_dialog implementation + + inline internal::file_dialog::file_dialog(type in_type, std::string const& title, + std::string const& default_path /* = "" */, + std::vector const& filters /* = {} */, opt options /* = opt::none */) + { + #if _WIN32 + std::string filter_list; + std::regex whitespace(" *"); + for (size_t i = 0; i + 1 < filters.size(); i += 2) + { + filter_list += filters[i] + '\0'; + filter_list += std::regex_replace(filters[i + 1], whitespace, ";") + '\0'; + } + filter_list += '\0'; + + m_async->start_func([this, in_type, title, default_path, filter_list, options]( + int* exit_code) -> std::string { + (void)exit_code; + m_wtitle = internal::str2wstr(title); + m_wdefault_path = internal::str2wstr(default_path); + auto wfilter_list = internal::str2wstr(filter_list); + + // Initialise COM. This is required for the new folder selection window, + // (see https://github.com/samhocevar/portable-file-dialogs/pull/21) + // and to avoid random crashes with GetOpenFileNameW() (see + // https://github.com/samhocevar/portable-file-dialogs/issues/51) + ole32_dll ole32; + + // Folder selection uses a different method + if (in_type == type::folder) + { + #if PFD_HAS_IFILEDIALOG + if (flags(flag::is_vista)) + { + // On Vista and higher we should be able to use IFileDialog for folder selection + IFileDialog* ifd; + HRESULT hr = + dll::proc( + ole32, "CoCreateInstance")(CLSID_FileOpenDialog, nullptr, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&ifd)); + + // In case CoCreateInstance fails (which it should not), try legacy approach + if (SUCCEEDED(hr)) + return select_folder_vista(ifd, options & opt::force_path); + } + #endif + + BROWSEINFOW bi; + memset(&bi, 0, sizeof(bi)); + + bi.lpfn = &bffcallback; + bi.lParam = (LPARAM)this; + + if (flags(flag::is_vista)) + { + if (ole32.is_initialized()) + bi.ulFlags |= BIF_NEWDIALOGSTYLE; + bi.ulFlags |= BIF_EDITBOX; + bi.ulFlags |= BIF_STATUSTEXT; + } + + auto* list = SHBrowseForFolderW(&bi); + std::string ret; + if (list) + { + auto buffer = new wchar_t[MAX_PATH]; + SHGetPathFromIDListW(list, buffer); + dll::proc(ole32, "CoTaskMemFree")(list); + ret = internal::wstr2str(buffer); + delete[] buffer; + } + return ret; + } + + OPENFILENAMEW ofn; + memset(&ofn, 0, sizeof(ofn)); + ofn.lStructSize = sizeof(OPENFILENAMEW); + ofn.hwndOwner = GetActiveWindow(); + + ofn.lpstrFilter = wfilter_list.c_str(); + + auto woutput = std::wstring(MAX_PATH * 256, L'\0'); + ofn.lpstrFile = (LPWSTR)woutput.data(); + ofn.nMaxFile = (DWORD)woutput.size(); + if (!m_wdefault_path.empty()) + { + // If a directory was provided, use it as the initial directory. If + // a valid path was provided, use it as the initial file. Otherwise, + // let the Windows API decide. + auto path_attr = GetFileAttributesW(m_wdefault_path.c_str()); + if (path_attr != INVALID_FILE_ATTRIBUTES && (path_attr & FILE_ATTRIBUTE_DIRECTORY)) + ofn.lpstrInitialDir = m_wdefault_path.c_str(); + else if (m_wdefault_path.size() <= woutput.size()) + // second argument is size of buffer, not length of string + StringCchCopyW(ofn.lpstrFile, MAX_PATH * 256 + 1, m_wdefault_path.c_str()); + else + { + ofn.lpstrFileTitle = (LPWSTR)m_wdefault_path.data(); + ofn.nMaxFileTitle = (DWORD)m_wdefault_path.size(); + } + } + ofn.lpstrTitle = m_wtitle.c_str(); + ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER; + + dll comdlg32("comdlg32.dll"); + + // Apply new visual style (required for windows XP) + new_style_context ctx; + + if (in_type == type::save) + { + if (!(options & opt::force_overwrite)) + ofn.Flags |= OFN_OVERWRITEPROMPT; + + dll::proc get_save_file_name( + comdlg32, "GetSaveFileNameW"); + if (get_save_file_name(&ofn) == 0) + return ""; + return internal::wstr2str(woutput.c_str()); + } + else + { + if (options & opt::multiselect) + ofn.Flags |= OFN_ALLOWMULTISELECT; + ofn.Flags |= OFN_PATHMUSTEXIST; + + dll::proc get_open_file_name( + comdlg32, "GetOpenFileNameW"); + if (get_open_file_name(&ofn) == 0) + return ""; + } + + std::string prefix; + for (wchar_t const* p = woutput.c_str(); *p;) + { + auto filename = internal::wstr2str(p); + p += wcslen(p); + // In multiselect mode, we advance p one wchar further and + // check for another filename. If there is one and the + // prefix is empty, it means we just read the prefix. + if ((options & opt::multiselect) && *++p && prefix.empty()) + { + prefix = filename + "/"; + continue; + } + + m_vector_result.push_back(prefix + filename); + } + + return ""; + }); + #elif __EMSCRIPTEN__ + // FIXME: do something + (void)in_type; + (void)title; + (void)default_path; + (void)filters; + (void)options; + #else + auto command = desktop_helper(); + + if (is_osascript()) + { + std::string script = "set ret to choose"; + switch (in_type) + { + case type::save: script += " file name"; break; + case type::open: + default: + script += " file"; + if (options & opt::multiselect) + script += " with multiple selections allowed"; + break; + case type::folder: script += " folder"; break; + } + + if (default_path.size()) + { + if (in_type == type::folder || is_directory(default_path)) + script += " default location "; + else + script += " default name "; + script += osascript_quote(default_path); + } + + script += " with prompt " + osascript_quote(title); + + if (in_type == type::open) + { + // Concatenate all user-provided filter patterns + std::string patterns; + for (size_t i = 0; i < filters.size() / 2; ++i) + patterns += " " + filters[2 * i + 1]; + + // Split the pattern list to check whether "*" is in there; if it + // is, we have to disable filters because there is no mechanism in + // OS X for the user to override the filter. + std::regex sep("\\s+"); + std::string filter_list; + bool has_filter = true; + std::sregex_token_iterator iter(patterns.begin(), patterns.end(), sep, -1); + std::sregex_token_iterator end; + for (; iter != end; ++iter) + { + auto pat = iter->str(); + if (pat == "*" || pat == "*.*") + has_filter = false; + else if (internal::starts_with(pat, "*.")) + filter_list += "," + osascript_quote(pat.substr(2, pat.size() - 2)); + } + + if (has_filter && filter_list.size() > 0) + { + // There is a weird AppleScript bug where file extensions of length != 3 are + // ignored, e.g. type{"txt"} works, but type{"json"} does not. Fortunately if + // the whole list starts with a 3-character extension, everything works again. + // We use "///" for such an extension because we are sure it cannot appear in + // an actual filename. + script += " of type {\"///\"" + filter_list + "}"; + } + } + + if (in_type == type::open && (options & opt::multiselect)) + { + script += "\nset s to \"\""; + script += "\nrepeat with i in ret"; + script += "\n set s to s & (POSIX path of i) & \"\\n\""; + script += "\nend repeat"; + script += "\ncopy s to stdout"; + } + else + { + script += "\nPOSIX path of ret"; + } + + command.push_back("-e"); + command.push_back(script); + } + else if (is_zenity()) + { + command.push_back("--file-selection"); + + // If the default path is a directory, make sure it ends with "/" otherwise zenity will + // open the file dialog in the parent directory. + auto filename_arg = "--filename=" + default_path; + if (in_type != type::folder && !ends_with(default_path, "/") + && internal::is_directory(default_path)) + filename_arg += "/"; + command.push_back(filename_arg); + + command.push_back("--title"); + command.push_back(title); + command.push_back("--separator=\n"); + + for (size_t i = 0; i < filters.size() / 2; ++i) + { + command.push_back("--file-filter"); + command.push_back(filters[2 * i] + "|" + filters[2 * i + 1]); + } + + if (in_type == type::save) + command.push_back("--save"); + if (in_type == type::folder) + command.push_back("--directory"); + if (!(options & opt::force_overwrite)) + command.push_back("--confirm-overwrite"); + if (options & opt::multiselect) + command.push_back("--multiple"); + } + else if (is_kdialog()) + { + switch (in_type) + { + case type::save: command.push_back("--getsavefilename"); break; + case type::open: command.push_back("--getopenfilename"); break; + case type::folder: command.push_back("--getexistingdirectory"); break; + } + if (options & opt::multiselect) + { + command.push_back("--multiple"); + command.push_back("--separate-output"); + } + + command.push_back(default_path); + + std::string filter; + for (size_t i = 0; i < filters.size() / 2; ++i) + filter += (i == 0 ? "" : " | ") + filters[2 * i] + "(" + filters[2 * i + 1] + ")"; + command.push_back(filter); + + command.push_back("--title"); + command.push_back(title); + } + + if (flags(flag::is_verbose)) + std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); + #endif + } + + inline std::string internal::file_dialog::string_result() + { + #if _WIN32 + return m_async->result(); + #else + auto ret = m_async->result(); + // Strip potential trailing newline (zenity). Also strip trailing slash + // added by osascript for consistency with other backends. + while (!ret.empty() && (ret.back() == '\n' || ret.back() == '/')) + ret.pop_back(); + return ret; + #endif + } + + inline std::vector internal::file_dialog::vector_result() + { + #if _WIN32 + m_async->result(); + return m_vector_result; + #else + std::vector ret; + auto result = m_async->result(); + for (;;) + { + // Split result along newline characters + auto i = result.find('\n'); + if (i == 0 || i == std::string::npos) + break; + ret.push_back(result.substr(0, i)); + result = result.substr(i + 1, result.size()); + } + return ret; + #endif + } + + #if _WIN32 + // Use a static function to pass as BFFCALLBACK for legacy folder select + inline int CALLBACK internal::file_dialog::bffcallback( + HWND hwnd, UINT uMsg, LPARAM, LPARAM pData) + { + auto inst = (file_dialog*)pData; + switch (uMsg) + { + case BFFM_INITIALIZED: + SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM)inst->m_wdefault_path.c_str()); + break; + } + return 0; + } + + #if PFD_HAS_IFILEDIALOG + inline std::string internal::file_dialog::select_folder_vista(IFileDialog* ifd, bool force_path) + { + std::string result; + + IShellItem* folder; + + // Load library at runtime so app doesn't link it at load time (which will fail on windows + // XP) + dll shell32("shell32.dll"); + dll::proc create_item( + shell32, "SHCreateItemFromParsingName"); + + if (!create_item) + return ""; + + auto hr = create_item(m_wdefault_path.c_str(), nullptr, IID_PPV_ARGS(&folder)); + + // Set default folder if found. This only sets the default folder. If + // Windows has any info about the most recently selected folder, it + // will display it instead. Generally, calling SetFolder() to set the + // current directory “is not a good or expected user experience and + // should therefore be avoided”: + // https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfolder + if (SUCCEEDED(hr)) + { + if (force_path) + ifd->SetFolder(folder); + else + ifd->SetDefaultFolder(folder); + folder->Release(); + } + + // Set the dialog title and option to select folders + ifd->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM); + ifd->SetTitle(m_wtitle.c_str()); + + hr = ifd->Show(GetActiveWindow()); + if (SUCCEEDED(hr)) + { + IShellItem* item; + hr = ifd->GetResult(&item); + if (SUCCEEDED(hr)) + { + wchar_t* wname = nullptr; + // This is unlikely to fail because we use FOS_FORCEFILESYSTEM, but try + // to output a debug message just in case. + if (SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &wname))) + { + result = internal::wstr2str(std::wstring(wname)); + dll::proc(ole32_dll(), "CoTaskMemFree")(wname); + } + else + { + if (SUCCEEDED(item->GetDisplayName(SIGDN_NORMALDISPLAY, &wname))) + { + auto name = internal::wstr2str(std::wstring(wname)); + dll::proc(ole32_dll(), "CoTaskMemFree")(wname); + std::cerr << "pfd: failed to get path for " << name << std::endl; + } + else + std::cerr << "pfd: item of unknown type selected" << std::endl; + } + + item->Release(); + } + } + + ifd->Release(); + + return result; + } + #endif + #endif + + // notify implementation + + inline notify::notify( + std::string const& title, std::string const& message, icon _icon /* = icon::info */) + { + if (_icon == icon::question) // Not supported by notifications + _icon = icon::info; + + #if _WIN32 + // Use a static shared pointer for notify_icon so that we can delete + // it whenever we need to display a new one, and we can also wait + // until the program has finished running. + struct notify_icon_data : public NOTIFYICONDATAW + { + ~notify_icon_data() + { + Shell_NotifyIconW(NIM_DELETE, this); + } + }; + + static std::shared_ptr nid; + + // Release the previous notification icon, if any, and allocate a new + // one. Note that std::make_shared() does value initialization, so there + // is no need to memset the structure. + nid = nullptr; + nid = std::make_shared(); + + // For XP support + nid->cbSize = NOTIFYICONDATAW_V2_SIZE; + nid->hWnd = nullptr; + nid->uID = 0; + + // Flag Description: + // - NIF_ICON The hIcon member is valid. + // - NIF_MESSAGE The uCallbackMessage member is valid. + // - NIF_TIP The szTip member is valid. + // - NIF_STATE The dwState and dwStateMask members are valid. + // - NIF_INFO Use a balloon ToolTip instead of a standard ToolTip. The szInfo, uTimeout, + // szInfoTitle, and dwInfoFlags members are valid. + // - NIF_GUID Reserved. + nid->uFlags = NIF_MESSAGE | NIF_ICON | NIF_INFO; + + // Flag Description + // - NIIF_ERROR An error icon. + // - NIIF_INFO An information icon. + // - NIIF_NONE No icon. + // - NIIF_WARNING A warning icon. + // - NIIF_ICON_MASK Version 6.0. Reserved. + // - NIIF_NOSOUND Version 6.0. Do not play the associated sound. Applies only to balloon + // ToolTips + switch (_icon) + { + case icon::warning: nid->dwInfoFlags = NIIF_WARNING; break; + case icon::error: + nid->dwInfoFlags = NIIF_ERROR; + break; + /* case icon::info: */ default: + nid->dwInfoFlags = NIIF_INFO; + break; + } + + ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, + LONG_PTR lParam) -> BOOL { + ((NOTIFYICONDATAW*)lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName); + return false; + }; + + nid->hIcon = ::LoadIcon(nullptr, IDI_APPLICATION); + ::EnumResourceNames(nullptr, RT_GROUP_ICON, icon_enum_callback, (LONG_PTR)nid.get()); + + nid->uTimeout = 5000; + + StringCchCopyW( + nid->szInfoTitle, ARRAYSIZE(nid->szInfoTitle), internal::str2wstr(title).c_str()); + StringCchCopyW(nid->szInfo, ARRAYSIZE(nid->szInfo), internal::str2wstr(message).c_str()); + + // Display the new icon + Shell_NotifyIconW(NIM_ADD, nid.get()); + #elif __EMSCRIPTEN__ + // FIXME: do something + (void)title; + (void)message; + #else + auto command = desktop_helper(); + + if (is_osascript()) + { + command.push_back("-e"); + command.push_back("display notification " + osascript_quote(message) + " with title " + + osascript_quote(title)); + } + else if (is_zenity()) + { + command.push_back("--notification"); + command.push_back("--window-icon"); + command.push_back(get_icon_name(_icon)); + command.push_back("--text"); + command.push_back(title + "\n" + message); + } + else if (is_kdialog()) + { + command.push_back("--icon"); + command.push_back(get_icon_name(_icon)); + command.push_back("--title"); + command.push_back(title); + command.push_back("--passivepopup"); + command.push_back(message); + command.push_back("5"); + } + + if (flags(flag::is_verbose)) + std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); + #endif + } + + // message implementation + + inline message::message(std::string const& title, std::string const& text, + choice _choice /* = choice::ok_cancel */, icon _icon /* = icon::info */) + { + #if _WIN32 + // Use MB_SYSTEMMODAL rather than MB_TOPMOST to ensure the message window is brought + // to front. See https://github.com/samhocevar/portable-file-dialogs/issues/52 + UINT style = MB_SYSTEMMODAL; + switch (_icon) + { + case icon::warning: style |= MB_ICONWARNING; break; + case icon::error: style |= MB_ICONERROR; break; + case icon::question: + style |= MB_ICONQUESTION; + break; + /* case icon::info: */ default: + style |= MB_ICONINFORMATION; + break; + } + + switch (_choice) + { + case choice::ok_cancel: style |= MB_OKCANCEL; break; + case choice::yes_no: style |= MB_YESNO; break; + case choice::yes_no_cancel: style |= MB_YESNOCANCEL; break; + case choice::retry_cancel: style |= MB_RETRYCANCEL; break; + case choice::abort_retry_ignore: + style |= MB_ABORTRETRYIGNORE; + break; + /* case choice::ok: */ default: + style |= MB_OK; + break; + } + + m_mappings[IDCANCEL] = button::cancel; + m_mappings[IDOK] = button::ok; + m_mappings[IDYES] = button::yes; + m_mappings[IDNO] = button::no; + m_mappings[IDABORT] = button::abort; + m_mappings[IDRETRY] = button::retry; + m_mappings[IDIGNORE] = button::ignore; + + m_async->start_func([text, title, style](int* exit_code) -> std::string { + auto wtext = internal::str2wstr(text); + auto wtitle = internal::str2wstr(title); + // Apply new visual style (required for all Windows versions) + new_style_context ctx; + *exit_code = MessageBoxW(GetActiveWindow(), wtext.c_str(), wtitle.c_str(), style); + return ""; + }); + + #elif __EMSCRIPTEN__ + std::string full_message; + switch (_icon) + { + case icon::warning: full_message = "⚠️"; break; + case icon::error: full_message = "⛔"; break; + case icon::question: + full_message = "❓"; + break; + /* case icon::info: */ default: + full_message = "ℹ"; + break; + } + + full_message += ' ' + title + "\n\n" + text; + + // This does not really start an async task; it just passes the + // EM_ASM_INT return value to a fake start() function. + m_async->start(EM_ASM_INT( + { + if ($1) + return window.confirm(UTF8ToString($0)) ? 0 : -1; + alert(UTF8ToString($0)); + return 0; + }, + full_message.c_str(), _choice == choice::ok_cancel)); + #else + auto command = desktop_helper(); + + if (is_osascript()) + { + std::string script = + "display dialog " + osascript_quote(text) + " with title " + osascript_quote(title); + auto if_cancel = button::cancel; + switch (_choice) + { + case choice::ok_cancel: + script += + "buttons {\"OK\", \"Cancel\"}" + " default button \"OK\"" + " cancel button \"Cancel\""; + break; + case choice::yes_no: + script += + "buttons {\"Yes\", \"No\"}" + " default button \"Yes\"" + " cancel button \"No\""; + if_cancel = button::no; + break; + case choice::yes_no_cancel: + script += + "buttons {\"Yes\", \"No\", \"Cancel\"}" + " default button \"Yes\"" + " cancel button \"Cancel\""; + break; + case choice::retry_cancel: + script += + "buttons {\"Retry\", \"Cancel\"}" + " default button \"Retry\"" + " cancel button \"Cancel\""; + break; + case choice::abort_retry_ignore: + script += + "buttons {\"Abort\", \"Retry\", \"Ignore\"}" + " default button \"Abort\"" + " cancel button \"Retry\""; + if_cancel = button::retry; + break; + case choice::ok: + default: + script += + "buttons {\"OK\"}" + " default button \"OK\"" + " cancel button \"OK\""; + if_cancel = button::ok; + break; + } + m_mappings[1] = if_cancel; + m_mappings[256] = if_cancel; // XXX: I think this was never correct + script += " with icon "; + switch (_icon) + { + #define PFD_OSX_ICON(n) \ + "alias ((path to library folder from system domain) as text " \ + "& \"CoreServices:CoreTypes.bundle:Contents:Resources:" n ".icns\")" + case icon::info: + default: script += PFD_OSX_ICON("ToolBarInfo"); break; + case icon::warning: script += "caution"; break; + case icon::error: script += "stop"; break; + case icon::question: script += PFD_OSX_ICON("GenericQuestionMarkIcon"); break; + #undef PFD_OSX_ICON + } + + command.push_back("-e"); + command.push_back(script); + } + else if (is_zenity()) + { + switch (_choice) + { + case choice::ok_cancel: + command.insert( + command.end(), {"--question", "--cancel-label=Cancel", "--ok-label=OK"}); + break; + case choice::yes_no: + // Do not use standard --question because it causes “No” to return -1, + // which is inconsistent with the “Yes/No/Cancel” mode below. + command.insert(command.end(), + {"--question", "--switch", "--extra-button=No", "--extra-button=Yes"}); + break; + case choice::yes_no_cancel: + command.insert( + command.end(), {"--question", "--switch", "--extra-button=Cancel", + "--extra-button=No", "--extra-button=Yes"}); + break; + case choice::retry_cancel: + command.insert( + command.end(), {"--question", "--switch", "--extra-button=Cancel", + "--extra-button=Retry"}); + break; + case choice::abort_retry_ignore: + command.insert( + command.end(), {"--question", "--switch", "--extra-button=Ignore", + "--extra-button=Abort", "--extra-button=Retry"}); + break; + case choice::ok: + default: + switch (_icon) + { + case icon::error: command.push_back("--error"); break; + case icon::warning: command.push_back("--warning"); break; + default: command.push_back("--info"); break; + } + } + + command.insert(command.end(), + {"--title", title, "--width=300", "--height=0", // sensible defaults + "--no-markup", // do not interpret text as Pango markup + "--text", text, "--icon-name=dialog-" + get_icon_name(_icon)}); + } + else if (is_kdialog()) + { + if (_choice == choice::ok) + { + switch (_icon) + { + case icon::error: command.push_back("--error"); break; + case icon::warning: command.push_back("--sorry"); break; + default: command.push_back("--msgbox"); break; + } + } + else + { + std::string flag = "--"; + if (_icon == icon::warning || _icon == icon::error) + flag += "warning"; + flag += "yesno"; + if (_choice == choice::yes_no_cancel) + flag += "cancel"; + command.push_back(flag); + if (_choice == choice::yes_no || _choice == choice::yes_no_cancel) + { + m_mappings[0] = button::yes; + m_mappings[256] = button::no; + } + } + + command.push_back(text); + command.push_back("--title"); + command.push_back(title); + + // Must be after the above part + if (_choice == choice::ok_cancel) + command.insert(command.end(), {"--yes-label", "OK", "--no-label", "Cancel"}); + } + + if (flags(flag::is_verbose)) + std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); + #endif + } + + inline button message::result() + { + int exit_code; + auto ret = m_async->result(&exit_code); + // osascript will say "button returned:Cancel\n" + // and others will just say "Cancel\n" + if (internal::ends_with(ret, "Cancel\n")) + return button::cancel; + if (internal::ends_with(ret, "OK\n")) + return button::ok; + if (internal::ends_with(ret, "Yes\n")) + return button::yes; + if (internal::ends_with(ret, "No\n")) + return button::no; + if (internal::ends_with(ret, "Abort\n")) + return button::abort; + if (internal::ends_with(ret, "Retry\n")) + return button::retry; + if (internal::ends_with(ret, "Ignore\n")) + return button::ignore; + if (m_mappings.count(exit_code) != 0) + return m_mappings[exit_code]; + return exit_code == 0 ? button::ok : button::cancel; + } + + // open_file implementation + + inline open_file::open_file(std::string const& title, + std::string const& default_path /* = "" */, + std::vector const& filters /* = { "All Files", "*" } */, + opt options /* = opt::none */) + : file_dialog(type::open, title, default_path, filters, options) + {} + + inline open_file::open_file(std::string const& title, std::string const& default_path, + std::vector const& filters, bool allow_multiselect) + : open_file( + title, default_path, filters, (allow_multiselect ? opt::multiselect : opt::none)) + {} + + inline std::vector open_file::result() + { + return vector_result(); + } + + // save_file implementation + + inline save_file::save_file(std::string const& title, + std::string const& default_path /* = "" */, + std::vector const& filters /* = { "All Files", "*" } */, + opt options /* = opt::none */) + : file_dialog(type::save, title, default_path, filters, options) + {} + + inline save_file::save_file(std::string const& title, std::string const& default_path, + std::vector const& filters, bool confirm_overwrite) + : save_file( + title, default_path, filters, (confirm_overwrite ? opt::none : opt::force_overwrite)) + {} + + inline std::string save_file::result() + { + return string_result(); + } + + // select_folder implementation + + inline select_folder::select_folder(std::string const& title, + std::string const& default_path /* = "" */, opt options /* = opt::none */) + : file_dialog(type::folder, title, default_path, {}, options) + {} + + inline std::string select_folder::result() + { + return string_result(); + } + +#endif // PFD_SKIP_IMPLEMENTATION + +} // namespace pfd diff --git a/Include/Pipe/Extern/yyjson.c b/Src/Pipe/Extern/yyjson.c similarity index 99% rename from Include/Pipe/Extern/yyjson.c rename to Src/Pipe/Extern/yyjson.c index 4202d8a0..9255f5f0 100644 --- a/Include/Pipe/Extern/yyjson.c +++ b/Src/Pipe/Extern/yyjson.c @@ -20,7 +20,7 @@ SOFTWARE. *============================================================================*/ -#include "yyjson.h" +#include "Pipe/Extern/yyjson.h" #include diff --git a/Include/Pipe/Extern/yyjson.h b/Src/Pipe/Extern/yyjson.h similarity index 100% rename from Include/Pipe/Extern/yyjson.h rename to Src/Pipe/Extern/yyjson.h diff --git a/Src/PipeAlgorithms.cpp b/Src/PipeAlgorithms.cpp index 958c3714..61b5055c 100644 --- a/Src/PipeAlgorithms.cpp +++ b/Src/PipeAlgorithms.cpp @@ -2,7 +2,7 @@ #include "PipeAlgorithms.h" -#include "Pipe/Memory/Memory.h" +#include "PipeMemory.h" namespace p diff --git a/Src/PipeECS.cpp b/Src/PipeECS.cpp index 102fee5b..21ca8a9e 100644 --- a/Src/PipeECS.cpp +++ b/Src/PipeECS.cpp @@ -207,9 +207,13 @@ namespace p TFunction onWritePools, bool includeChildren) { if (includeChildren) + { RetrieveHierarchy(entities, ids); + } else + { ids = entities; + } MapIdsToIndices(); Next("count", ids.Size()); @@ -287,7 +291,7 @@ namespace p void Read(Reader& r, Id& val) { - auto* entityReader = dynamic_cast(&r); + auto* entityReader = p::Cast(&r); if (P_EnsureMsg(entityReader, "Serializing an ecs Id without an EntityReader")) [[likely]] { i32 dataId; @@ -298,7 +302,7 @@ namespace p void Write(Writer& w, Id val) { - auto* entityWriter = dynamic_cast(&w); + auto* entityWriter = p::Cast(&w); if (P_EnsureMsg(entityWriter, "Serializing an ecs Id without an EntityWriter")) [[likely]] { const i32* dataId = entityWriter->GetIdToIndexes().Find(val); @@ -396,7 +400,6 @@ namespace p { if (lastRemovedIndex == NO_INDEX) { - const auto last = end(); for (Id id : idList) { idIndices[GetIdIndex(id)] = NO_INDEX; @@ -681,7 +684,9 @@ namespace p } statics.Insert(index); if (bAdded) + { *bAdded = true; + } return statics[index]; } @@ -851,7 +856,9 @@ namespace p void FindAllIdsWith(TView pools, TArray& ids) { if (pools.IsEmpty()) + { return; + } for (const BasePool* pool : pools) { @@ -891,7 +898,9 @@ namespace p void FindAllIdsWithAny(TView pools, TArray& ids) { if (pools.IsEmpty()) + { return; + } for (const BasePool* pool : pools) { @@ -913,7 +922,9 @@ namespace p void FindAllIdsWithAnyUnique(TView pools, TArray& ids) { if (pools.IsEmpty()) + { return; + } i32 maxPossibleSize = 0; for (const BasePool* pool : pools) @@ -1100,7 +1111,7 @@ namespace p { for (Id child : cParent->children) { - access.Get(parent).parent = NoId; + access.Get(child).parent = NoId; } cParent->children.Clear(); } diff --git a/Src/PipeFiles.cpp b/Src/PipeFiles.cpp index 8b76fcfb..42f5c59b 100644 --- a/Src/PipeFiles.cpp +++ b/Src/PipeFiles.cpp @@ -6,6 +6,12 @@ #include "Pipe/Core/Log.h" #include "Pipe/Core/Map.h" #include "Pipe/Core/Tag.h" +#include "PipePlatform.h" + +#if P_PLATFORM_WINDOWS + // Include Windows before protable file dialogs + #include +#endif #include "Pipe/Extern/portable-file-dialogs.h" #include "Pipe/Files/Paths.h" @@ -115,7 +121,9 @@ namespace p { #if P_PLATFORM_WINDOWS == 0 if (getuid() == 0) // Is root? + { return true; + } #endif return (status.permissions() & std::filesystem::perms::owner_read) != std::filesystem::perms::none; @@ -324,7 +332,6 @@ namespace p void DirectorySnapshot::DeleteAll(DirectorySnapshotDiff& diff) { - FileStatus fs; for (const auto& value : files) { if (std::filesystem::is_directory(value.second)) @@ -661,14 +668,14 @@ namespace p dirWatch->Watch(); } - void GenericWatch::WatchDir(StringView path) + void GenericWatch::WatchDir(StringView pathToWatch) { - dirWatch->WatchDir(path); + dirWatch->WatchDir(pathToWatch); } - bool GenericWatch::IsPathInWatches(StringView path) const + bool GenericWatch::IsPathInWatches(StringView pathToCheck) const { - return dirWatch->IsPathInWatches(path); + return dirWatch->IsPathInWatches(pathToCheck); } diff --git a/Src/PipeMath.cpp b/Src/PipeMath.cpp index acda3f82..988435f8 100644 --- a/Src/PipeMath.cpp +++ b/Src/PipeMath.cpp @@ -73,7 +73,6 @@ namespace p float y = value - (2.0f * pi) * quotient; // Map y to [-pi/2,pi/2] with sin(y) = sin(value). - float sign; if (y > halfPi) { y = pi - y; diff --git a/Src/Memory/Memory.cpp b/Src/PipeMemory.cpp similarity index 54% rename from Src/Memory/Memory.cpp rename to Src/PipeMemory.cpp index e8eef303..df53331d 100644 --- a/Src/Memory/Memory.cpp +++ b/Src/PipeMemory.cpp @@ -1,16 +1,14 @@ // Copyright 2015-2024 Piperift - All rights reserved -#include "Pipe/Memory/Memory.h" +#include "PipeMemory.h" -#include "Pipe/Core/Checks.h" -#include "Pipe/Core/Utility.h" -#include "PipeMath.h" +#include "PipeMemoryArenas.h" #include - namespace p { +#pragma region Memory Ops void MoveMem(void* dest, void* src, sizet size) { memmove(dest, src, size); @@ -94,4 +92,127 @@ namespace p // Get padding with the header as an offset return headerSize + GetAlignmentPadding(static_cast(ptr) + headerSize, align); } +#pragma endregion Memory Ops + + +#pragma region Allocation + static Arena* currentArena = nullptr; + + void InitializeMemory() + { + GetHeapArena(); + } + + void* HeapAlloc(sizet size) + { + return malloc(size); + } + + void* HeapAlloc(sizet size, sizet align) + { +#if P_PLATFORM_WINDOWS + // TODO: Windows needs _aligned_free in order to use _aligned_alloc() + void* const ptr = malloc(size); +#else + void* const ptr = aligned_alloc(align, size); +#endif + return ptr; + } + + void* HeapRealloc(void* ptr, sizet size) + { + return realloc(ptr, size); + } + + void HeapFree(void* ptr) + { + free(ptr); + } + + + HeapArena& GetHeapArena() + { + static HeapArena heapArena{}; + return heapArena; + } + + Arena& GetCurrentArena() + { + return currentArena ? *currentArena : GetHeapArena(); + } + + void SetCurrentArena(Arena& arena) + { + currentArena = &arena; + } + + + void* Alloc(Arena& arena, sizet size) + { + return arena.Alloc(size); + } + + void* Alloc(Arena& arena, sizet size, sizet align) + { + return arena.Alloc(size, align); + } + + bool Realloc(Arena& arena, void* ptr, sizet ptrSize, sizet size) + { + return arena.Realloc(ptr, ptrSize, size); + } + + void Free(Arena& arena, void* ptr, sizet size) + { + arena.Free(ptr, size); + } + + void* Alloc(sizet size) + { + return Alloc(GetCurrentArena(), size); + } + + void* Alloc(sizet size, sizet align) + { + return Alloc(GetCurrentArena(), size, align); + } + + bool Realloc(void* ptr, sizet ptrSize, sizet size) + { + return Realloc(GetCurrentArena(), ptr, ptrSize, size); + } + + void Free(void* ptr, sizet size) + { + Free(GetCurrentArena(), ptr, size); + } +#pragma endregion Allocation + + +#pragma region Arena + ArenaBlock::ArenaBlock(ArenaBlock&& other) noexcept + { + data = Exchange(other.data, nullptr); + size = Exchange(other.size, 0); + } + ArenaBlock& ArenaBlock::operator=(ArenaBlock&& other) noexcept + { + data = Exchange(other.data, nullptr); + size = Exchange(other.size, 0); + return *this; + } + + + ChildArena::ChildArena(Arena* inParent) : parent{inParent} + { + if (!parent) + { + parent = &GetCurrentArena(); + if (parent == this) [[unlikely]] + { + parent = &GetHeapArena(); + } + } + } +#pragma endregion Arena } // namespace p diff --git a/Src/PipeMemoryArenas.cpp b/Src/PipeMemoryArenas.cpp new file mode 100644 index 00000000..675a6c43 --- /dev/null +++ b/Src/PipeMemoryArenas.cpp @@ -0,0 +1,690 @@ +// Copyright 2015-2024 Piperift - All rights reserved + +#include "PipeMemoryArenas.h" + + +namespace p +{ +#pragma region Heap Arena + HeapArena::HeapArena() + { + stats.name = "Heap Arena"; + Interface(); + } + + void* HeapArena::Alloc(const sizet size) + { + void* ptr = p::HeapAlloc(size); + stats.Add(ptr, size); + return ptr; + } + void* HeapArena::Alloc(const sizet size, const sizet align) + { + void* ptr = p::HeapAlloc(size, align); + stats.Add(ptr, size); + return ptr; + } + bool HeapArena::Realloc(void* ptr, const sizet ptrSize, const sizet size) + { + return false; + } + void HeapArena::Free(void* ptr, sizet size) + { + stats.Remove(ptr, size); + p::HeapFree(ptr); + } +#pragma endregion Heap Arena + +#pragma region Mono Linear + MonoLinearArena::MonoLinearArena(ArenaBlock externalBlock, Arena& parentArena) + : ChildArena(&parentArena), insert{externalBlock.data}, block{Move(externalBlock)} + { + stats.name = "Mono Linear Arena"; + Interface(); + } + + MonoLinearArena::MonoLinearArena(const sizet blockSize, Arena& parentArena) + : ChildArena(&parentArena) + , insert{GetParentArena().Alloc(blockSize)} + , block{insert, blockSize} + , selfAllocated{true} + { + stats.name = "Mono Linear Arena"; + Interface(); + } + + void* MonoLinearArena::Alloc(sizet size) + { + // TODO: Resolve a reasonable align size based on allocation size + return Alloc(size, alignof(std::max_align_t)); + } + + void* MonoLinearArena::Alloc(sizet size, sizet align) + { + u8* const allocEnd = (u8*)insert + size + GetAlignmentPadding(insert, align); + void* ptr; + // Not enough space in current block? + if (allocEnd <= block.End()) [[likely]] + { + insert = allocEnd; + ++count; + ptr = allocEnd - size; // Fast-path + } + else + { + // Allocation doesn't fit. Allocate in parent arena + ptr = GetParentArena().Alloc(size, align); + } + stats.Add(ptr, size); + return ptr; + } + + void MonoLinearArena::Free(void* ptr, sizet size) + { + stats.Remove(ptr, size); + if (ptr >= block.data && ptr < block.End()) [[likely]] + { + --count; + if (count <= 0) [[unlikely]] + { + Release(true); + } + } + else + { + GetParentArena().Free(ptr, size); + } + } + + void MonoLinearArena::Release(bool keepIfSelfAllocated) + { + stats.Release(); + insert = block.data; + count = 0; + if (selfAllocated && !keepIfSelfAllocated) + { + // Self allcoated block gets freed + GetParentArena().Free(block.data, block.size); + block = {}; + } + } +#pragma endregion Mono Linear + +#pragma region Multi Linear + template + void Details::LinearBasePool::AllocateBlock(Arena& parentArena) + { + LinearBlock* const lastBlock = freeBlock; + + // Allocate aligning by blockSize + void* ptr = parentArena.Alloc(blockSize + sizeof(LinearBlock), GetBlockSize()); + + void* blockPtr = (u8*)ptr + GetAlignmentPadding(ptr, GetBlockSize()); + freeBlock = new (blockPtr) LinearBlock(); + insert = freeBlock + 1; + freeBlock->count = 1; // Count +1 prevents Free cleaning blocks being filled + freeBlock->last = lastBlock; + freeBlock->unaligned = ptr; + + if (lastBlock) + { + lastBlock->next = freeBlock; + // Dismark block allowing freeing it: + --lastBlock->count; + // In theory, we would need to free lastBlock if full here... + // But we only reach AllocateBlock if the previous one is full! + } + } + + template + void Details::LinearBasePool::FreeBlock(Arena& parentArena, LinearBlock* block) + { + P_Check(block); + // freeBlock is never freed. No need to handle it. + if (block->next) + { + block->next->last = block->last; + } + if (block->last) + { + block->last->next = block->next; + } + parentArena.Free(block->unaligned, GetAllocatedBlockSize()); + } + + template + void* Details::LinearBasePool::Alloc(Arena& parentArena, sizet size, sizet align) + { + insert = (u8*)insert + size + GetAlignmentPadding(insert, align); + + // Not enough space in current block? + if (freeBlock && insert <= GetBlockEnd(freeBlock)) [[likely]] + { + ++freeBlock->count; + return (u8*)insert - size; // Fast-path + } + + AllocateBlock(parentArena); + // Recalculate allocation with new block + insert = (u8*)insert + size + GetAlignmentPadding(insert, align); + ++freeBlock->count; + return (u8*)insert - size; + } + + template + void Details::LinearBasePool::Free(Arena& parentArena, void* ptr, sizet size) + { + auto* block = static_cast(GetAlignedBlock(ptr, GetBlockSize())); + if (!block) [[unlikely]] + { + return; + } + + --block->count; + if (block->count <= 0) [[unlikely]] + { + // If the block is empty and was marked full, free it + FreeBlock(parentArena, block); + } + } + + template + void Details::LinearBasePool::Release(Arena& parentArena) + { + // Iterate backwards all blocks while freeing them + insert = nullptr; + while (freeBlock != nullptr) + { + LinearBlock* const block = freeBlock; + freeBlock = freeBlock->last; + parentArena.Free(block->unaligned, GetAllocatedBlockSize()); + } + freeBlock = nullptr; + } + + + MultiLinearArena::MultiLinearArena(Arena& parentArena) : ChildArena(&parentArena) + { + Interface(); + } + + void* MultiLinearArena::Alloc(sizet size) + { + // TODO: Resolve a reasonable align size based on allocation size + return Alloc(size, alignof(std::max_align_t)); + } + + void* MultiLinearArena::Alloc(sizet size, sizet align) + { + if (size < smallPool.maxSize) + { + return smallPool.Alloc(GetParentArena(), size, align); + } + else if (size < mediumPool.maxSize) + { + return mediumPool.Alloc(GetParentArena(), size, align); + } + else if (size < bigPool.maxSize) + { + return bigPool.Alloc(GetParentArena(), size, align); + } + else + { + return GetParentArena().Alloc(size, align); + } + } + + void MultiLinearArena::Free(void* ptr, sizet size) + { + if (size < smallPool.maxSize) + { + smallPool.Free(GetParentArena(), ptr, size); + } + else if (size < mediumPool.maxSize) + { + mediumPool.Free(GetParentArena(), ptr, size); + } + else if (size < bigPool.maxSize) + { + bigPool.Free(GetParentArena(), ptr, size); + } + else + { + GetParentArena().Free(ptr, size); + } + } + + void MultiLinearArena::Release() + { + smallPool.Release(GetParentArena()); + mediumPool.Release(GetParentArena()); + bigPool.Release(GetParentArena()); + } +#pragma endregion Multi Linear + +#pragma region Best Fit Arena + bool operator==(const BestFitArena::Slot& a, sizet b) + { + return a.size == b; + } + bool operator<(const BestFitArena::Slot& a, sizet b) + { + return a.size < b; + } + bool operator>(const BestFitArena::Slot& a, sizet b) + { + return a.size > b; + } + bool operator<=(const BestFitArena::Slot& a, sizet b) + { + return a.size <= b; + } + bool operator>=(const BestFitArena::Slot& a, sizet b) + { + return a.size >= b; + } + bool operator==(sizet a, const BestFitArena::Slot& b) + { + return a == b.size; + } + bool operator<(sizet a, const BestFitArena::Slot& b) + { + return a < b.size; + } + bool operator>(sizet a, const BestFitArena::Slot& b) + { + return a > b.size; + } + bool operator<=(sizet a, const BestFitArena::Slot& b) + { + return a <= b.size; + } + bool operator>=(sizet a, const BestFitArena::Slot& b) + { + return a >= b.size; + } + + + BestFitArena::BestFitArena(Arena* parent, const sizet initialSize) : ChildArena(parent) + { + Interface(); + + P_Check(initialSize > 0); + block.data = GetParentArena().Alloc(initialSize); + block.size = initialSize; + // Set address at end of block. Size is 0 + // freeSlots.SetData(static_cast(block.GetData()) + block.Size()); + // Add first slot for the entire block + freeSlots.Add({reinterpret_cast(block.data), block.size}); + + freeSize = initialSize; + } + + BestFitArena::~BestFitArena() + { + p::Free(block.data, block.size); + block.data = nullptr; + } + + void* BestFitArena::Alloc(const sizet size) + { + const i32 slotIndex = FindSmallestSlot(size); + if (slotIndex == NO_INDEX || slotIndex >= freeSlots.Size()) [[unlikely]] + { + // Error("Couldn't fit {} bytes", size); + return nullptr; + } + + Slot& slot = freeSlots[slotIndex]; + u8* const start = slot.start; + u8* const end = start + size; + + ReduceSlot(slotIndex, slot, start, end); + freeSize -= size; + return start; + } + + void* BestFitArena::Alloc(const sizet size, sizet alignment) + { + // Maximum size needed, based on worst possible alignment: + const i32 slotIndex = FindSmallestSlot(size + (alignment - 1)); + if (slotIndex == NO_INDEX || slotIndex >= freeSlots.Size()) [[unlikely]] + { + // Error("Couldn't fit {} bytes", size); + return nullptr; + } + + Slot& slot = freeSlots[slotIndex]; + u8* const start = slot.start + GetAlignmentPadding(slot.start, alignment); + u8* const end = start + size; + + ReduceSlot(slotIndex, slot, start, end); + freeSize -= size; + return start; + } + + void BestFitArena::Free(void* ptr, sizet size) + { + if (ptr) + { + u8* const allocationStart = static_cast(ptr); + u8* const allocationEnd = allocationStart + size; + freeSize += allocationEnd - allocationStart; + AbsorbFreeSpace(allocationStart, allocationEnd); + } + } + + i32 BestFitArena::FindSmallestSlot(sizet neededSize) + { + if (pendingSort) [[unlikely]] + { + pendingSort = false; + if ((float(freeSlots.Size()) / freeSlots.Capacity()) < 0.1f) + { + // Dont shrink until there is 90% of unused space + freeSlots.Shrink(); + } + // Sort slots by size. Small first + freeSlots.Sort(TGreater<>()); + } + + // Find smallest slot fitting our required size + return freeSlots.FindSortedMin(neededSize, true); + // return freeSlots.UpperBound(neededSize); + } + + void BestFitArena::ReduceSlot( + i32 slotIndex, Slot& slot, u8* const allocationStart, u8* const allocationEnd) + { + if (allocationEnd == slot.End()) // Slot would become empty + { + if (allocationStart > slot.start) // Slot can still fill alignment gap + { + slot.size = allocationStart - slot.start; + pendingSort = true; + } + else + { + freeSlots.RemoveAtUnsafe(slotIndex, false); + } + return; + } + + u8* const oldSlotStart = slot.start; + slot.start = allocationEnd; + slot.size += oldSlotStart - allocationEnd; + + // If alignment leaves a gap in the slot, save this space as a new slot + if (allocationStart > oldSlotStart) + { + freeSlots.Add({oldSlotStart, sizet(allocationStart - oldSlotStart)}); + pendingSort = true; + } + // If slot is smaller than prev slot, we have to sort + else if (slotIndex > 0 && slot.size < freeSlots[slotIndex - 1].size) + { + pendingSort = true; + } + } + + void BestFitArena::AbsorbFreeSpace(u8* const allocationStart, u8* const allocationEnd) + { + // Find previous and/or next slots + i32 previousSlot = NO_INDEX; + i32 nextSlot = NO_INDEX; + for (i32 i = 0; i < freeSlots.Size(); ++i) + { + const Slot& slot = freeSlots[i]; + if (slot.start == allocationEnd) + { + nextSlot = i; + if (previousSlot != NO_INDEX) + { + break; // We found both slots + } + } + else if (slot.End() == allocationStart) + { + previousSlot = i; + if (nextSlot != NO_INDEX) + { + break; // We found both slots + } + } + } + + if (previousSlot != NO_INDEX && nextSlot != NO_INDEX) + { + // Expand next slot to the start of the previous slot + const Slot& next = freeSlots[nextSlot]; + Slot& previous = freeSlots[previousSlot]; + previous.size = next.End() - previous.start; + + freeSlots.RemoveAtSwapUnsafe(nextSlot); + } + else if (previousSlot != NO_INDEX) + { + Slot& previous = freeSlots[previousSlot]; + previous.size = allocationEnd - previous.start; + } + else if (nextSlot != NO_INDEX) + { + Slot& next = freeSlots[nextSlot]; + next.size += next.start - allocationStart; + next.start = allocationStart; + } + else + { + freeSlots.Add({allocationStart, sizet(allocationEnd - allocationStart)}); + } + pendingSort = true; + } +#pragma endregion Best Fit Arena + +#pragma region Big Best Fit Arena + bool operator==(const BigBestFitArena::Slot& a, u32 b) + { + return a.size == b; + } + auto operator<=>(const BigBestFitArena::Slot& a, u32 b) + { + return a.size <=> b; + } + bool operator==(u32 a, const BigBestFitArena::Slot& b) + { + return a == b.size; + } + auto operator<=>(u32 a, const BigBestFitArena::Slot& b) + { + return a <=> b.size; + } + + BigBestFitArena::BigBestFitArena(Arena* parent, const sizet initialSize) : ChildArena(parent) + { + Interface(); + + P_Check(initialSize > 0); + block.data = GetParentArena().Alloc(initialSize); + block.size = initialSize; + // Set address at end of block. Size is 0 + // freeSlots.SetData(static_cast(block.GetData()) + block.Size()); + // Add first slot for the entire block + freeSlots.Add({0, u32(block.size)}); + + freeSize = initialSize; + } + + BigBestFitArena::~BigBestFitArena() + { + p::Free(block.data, block.size); + block.data = nullptr; + } + + void* BigBestFitArena::Alloc(const sizet size) + { + return Alloc(size, minAlignment); // Always align by header size + } + + void* BigBestFitArena::Alloc(const sizet size, sizet alignment) + { + // We always use at least 8 bytes of alignment for the header + alignment = Max(alignment, minAlignment); + + const i32 slotIndex = FindSmallestSlot(size + alignment - 1); + if (slotIndex == NO_INDEX || slotIndex >= freeSlots.Size()) + { + // Error("No slots can fit {} bytes!", size); + return nullptr; + } + + Slot& slot = freeSlots[slotIndex]; + + u8* slotStart = (u8*)block.data + slot.offset; + u8* const ptr = + slotStart + + GetAlignmentPaddingWithHeader(slotStart, alignment, sizeof(AllocationHeader)); + + auto* const header = GetHeader(ptr); + header->end = ptr + size; + header->end += GetAlignmentPadding(header->end, minAlignment); // Align end by 8 + + ReduceSlot( + slotIndex, slot, ToOffset(header, block.data), ToOffset(header->end, block.data)); + freeSize -= static_cast(header->end) - reinterpret_cast(header); + return ptr; + } + + void BigBestFitArena::Free(void* ptr, sizet size) + { + if (ptr) + { + auto* const header = GetHeader(ptr); + u8* const allocationStart = reinterpret_cast(header); + u8* const allocationEnd = header->end; + + freeSize += allocationEnd - allocationStart; + AbsorbFreeSpace( + ToOffset(allocationStart, block.data), ToOffset(allocationEnd, block.data)); + } + } + + i32 BigBestFitArena::FindSmallestSlot(sizet neededSize) + { + if (pendingSort) [[unlikely]] + { + pendingSort = false; + if (float(freeSlots.Size()) / freeSlots.Capacity() < 0.25f) + { + // Dont shrink until there is 75% of unused space + freeSlots.Shrink(); + } + + // Sort slots by size. Small first + freeSlots.Sort(TGreater<>()); + } + + // Find smallest slot fitting our required size + return freeSlots.FindSortedMin(neededSize, true); + } // namespace p + + void BigBestFitArena::ReduceSlot( + i32 slotIndex, Slot& slot, u32 allocationStart, u32 allocationEnd) + { + if (allocationEnd == slot.offset + slot.size) // Slot would become empty + { + if (allocationStart > slot.offset) // Slot can still fill alignment gap + { + slot.size = allocationStart - slot.offset; + pendingSort = true; + } + else + { + freeSlots.RemoveAtUnsafe(slotIndex, false); + } + return; + } + + u32 offset = slot.offset; + slot.offset = allocationEnd; + slot.size += offset - allocationEnd; + + // If alignment leaves a gap in the slot, save this space as a new slot + if (allocationStart > offset) + { + freeSlots.Add({offset, allocationStart - offset}); + } + // If slot is smaller than prev slot, we have to sort + else if (slotIndex > 0 && slot.size < freeSlots[slotIndex - 1].size) + { + pendingSort = true; + } + } + + void BigBestFitArena::AbsorbFreeSpace(u32 allocationStart, u32 allocationEnd) + { + // Find previous and/or next slots + i32 previousSlot = NO_INDEX; + i32 nextSlot = NO_INDEX; + for (i32 i = 0; i < freeSlots.Size(); ++i) + { + const Slot& slot = freeSlots[i]; + if (slot.offset == allocationEnd) + { + nextSlot = i; + if (previousSlot != NO_INDEX) + { + break; // We found both slots + } + } + else if ((slot.offset + slot.size) == allocationStart) + { + previousSlot = i; + if (nextSlot != NO_INDEX) + { + break; // We found both slots + } + } + } + + if (previousSlot != NO_INDEX && nextSlot != NO_INDEX) + { + // Expand next slot to the start of the previous slot + Slot& next = freeSlots[nextSlot]; + Slot& previous = freeSlots[previousSlot]; + const Slot combined{previous.offset, (next.offset - previous.offset) + next.size}; + // Remove the smallest slot, expand the other + if (next.size > previous.size) + { + next = combined; + freeSlots.RemoveAtSwapUnsafe(previousSlot); + } + else + { + previous = combined; + freeSlots.RemoveAtSwapUnsafe(nextSlot); + } + } + else if (previousSlot != NO_INDEX) + { + Slot& previous = freeSlots[previousSlot]; + previous.size = allocationEnd - previous.offset; + } + else if (nextSlot != NO_INDEX) + { + Slot& next = freeSlots[nextSlot]; + next.size += next.offset - allocationStart; + next.offset = allocationStart; + } + else + { + freeSlots.Add({allocationStart, allocationEnd - allocationStart}); + } + pendingSort = true; + } + + u32 BigBestFitArena::ToOffset(void* data, void* block) + { + return u32(static_cast(data) - static_cast(block)); + } +#pragma endregion Big Best Fit Arena +} // namespace p diff --git a/Src/Core/GenericPlatformMisc.cpp b/Src/PipePlatform.cpp similarity index 84% rename from Src/Core/GenericPlatformMisc.cpp rename to Src/PipePlatform.cpp index 5233501b..4653de6d 100644 --- a/Src/Core/GenericPlatformMisc.cpp +++ b/Src/PipePlatform.cpp @@ -1,15 +1,15 @@ // Copyright 2015-2024 Piperift - All rights reserved -#include "Pipe/Core/GenericPlatformMisc.h" +#include "PipePlatform.h" #include "Pipe/Core/Guid.h" -#include "Pipe/Core/PlatformMisc.h" #include "PipeTime.h" +#pragma region Platform Misc namespace p { - void GenericPlatformMisc::CreateGuid(Guid& guid) + void PlatformMisc::CreateGuid(Guid& guid) { static u16 incrementCounter = 0; static DateTime initialDateTime; @@ -32,9 +32,10 @@ namespace p estimatedCurrentDateTime.GetTicks() & 0xffffffff, PlatformMisc::GetCycles()); } - u64 GenericPlatformMisc::GetCycles64() + u64 PlatformMisc::GetCycles64() { return Chrono::floor(DateTime::Now().GetTime().time_since_epoch()) .count(); } } // namespace p +#pragma endregion Platform Misc diff --git a/Src/PipeReflect.cpp b/Src/PipeReflect.cpp index b876d77b..9d92e3f4 100644 --- a/Src/PipeReflect.cpp +++ b/Src/PipeReflect.cpp @@ -3,11 +3,10 @@ #include "PipeReflect.h" #include "Pipe/Core/Checks.h" -#include "Pipe/Core/Platform.h" #include "Pipe/Core/TypeTraits.h" -#include "Pipe/Memory/HeapArena.h" -#include "Pipe/Memory/MultiLinearArena.h" #include "PipeECS.h" +#include "PipeMemoryArenas.h" +#include "PipePlatform.h" namespace p @@ -198,7 +197,9 @@ namespace p { currentId = GetTypeParent(currentId); if (parentId == currentId) + { return true; + } } while (currentId.IsValid()); return false; } @@ -388,11 +389,6 @@ namespace p GetRegistry().operations[currentEdit.index] = operations; } - - TypeId BaseObject::GetTypeId() const - { - return static_cast(this)->GetTypeId(); - } TPtr BaseObject::AsPtr() const { return static_cast(this)->AsPtr(); @@ -401,7 +397,10 @@ namespace p TPtr ObjectOwnership::nextOwner{}; - ObjectOwnership::ObjectOwnership() : self{}, owner{Move(nextOwner)} {} + ObjectOwnership::ObjectOwnership() : self{}, owner{Move(nextOwner)} + { + ; + } const TPtr& ObjectOwnership::AsPtr() const { return self; diff --git a/Src/PipeSerialize.cpp b/Src/PipeSerialize.cpp index de64085e..533df134 100644 --- a/Src/PipeSerialize.cpp +++ b/Src/PipeSerialize.cpp @@ -10,9 +10,6 @@ #include "Pipe/Extern/yyjson.h" #include "PipeMath.h" -// Include yyjson.c to get it compiled -#include "Pipe/Extern/yyjson.c" - static void* yyjson_malloc(void* ctx, p::sizet size) { @@ -33,7 +30,9 @@ bool yyjson_mut_obj_add_val( yyjson_mut_doc* doc, yyjson_mut_val* obj, p::StringView _key, yyjson_mut_val* _val) { if (yyjson_unlikely(!_val)) + { return false; + } if (yyjson_likely(yyjson_mut_is_obj(obj) && _key.data())) { @@ -754,7 +753,7 @@ namespace p if (asString.data()) { - yyjsonAllocator.free(yyjsonAllocator.ctx, const_cast(asString.data())); + yyjsonAllocator.free(yyjsonAllocator.ctx, const_cast(asString.data())); } } @@ -827,7 +826,6 @@ namespace p void JsonFormatWriter::BeginObject() { - Scope& scope = GetScope(); if (current) [[unlikely]] { if (!unsafe_yyjson_is_obj(current)) [[unlikely]] @@ -844,7 +842,6 @@ namespace p void JsonFormatWriter::BeginArray(u32 size) { - Scope& scope = GetScope(); if (current) [[unlikely]] { if (!unsafe_yyjson_is_arr(current)) [[unlikely]] @@ -941,7 +938,7 @@ namespace p if (asString.data()) { // Free previous string value - yyjsonAllocator.free(yyjsonAllocator.ctx, const_cast(asString.data())); + yyjsonAllocator.free(yyjsonAllocator.ctx, const_cast(asString.data())); } yyjson_write_flag flags = pretty ? YYJSON_WRITE_PRETTY : 0; @@ -977,7 +974,7 @@ namespace p void BinaryFormatReader::Read(bool& val) { - val = *pointer; + val = bool(*pointer); ++pointer; P_CheckMsg(pointer <= data.EndData(), "The read buffer has been exceeded"); } @@ -1077,11 +1074,11 @@ namespace p { i32 size = 0; Read(size); - const sizet sizeInBytes = size * sizeof(TChar); + const sizet sizeInBytes = size * sizeof(char); if (P_EnsureMsg(pointer + sizeInBytes <= data.EndData(), "The size of a string readen exceeds the read buffer!")) [[likely]] { - val = StringView{reinterpret_cast(pointer), sizeInBytes}; + val = StringView{reinterpret_cast(pointer), sizeInBytes}; pointer += sizeInBytes; P_CheckMsg(pointer <= data.EndData(), "The read buffer has been exceeded"); } @@ -1114,9 +1111,9 @@ namespace p Free(arena, data, capacity); } - void BinaryFormatWriter::BeginArray(u32 size) + void BinaryFormatWriter::BeginArray(u32 arraySize) { - Write(size); + Write(arraySize); } bool BinaryFormatWriter::EnterNext(StringView) @@ -1227,17 +1224,17 @@ namespace p } void BinaryFormatWriter::Write(StringView val) { - const i32 valSize = i32(val.size() * sizeof(TChar)); + const i32 valSize = i32(val.size() * sizeof(char)); PreAlloc(valSize + sizeof(i32)); Write(i32(val.size())); - CopyMem(data + size, const_cast(val.data()), valSize); + CopyMem(data + size, const_cast(val.data()), valSize); size += valSize; } TView BinaryFormatWriter::GetData() { - return {data, size}; + return {data, i32(size)}; } void BinaryFormatWriter::PreAlloc(u32 offset) @@ -1281,36 +1278,30 @@ namespace p void Read(Reader& ct, Guid& guid) { ct.BeginObject(); - ct.Next(TX("a"), guid.a); - ct.Next(TX("b"), guid.b); - ct.Next(TX("c"), guid.c); - ct.Next(TX("d"), guid.d); + ct.Next("a", guid.a); + ct.Next("b", guid.b); + ct.Next("c", guid.c); + ct.Next("d", guid.d); } void Write(Writer& ct, const Guid& guid) { ct.BeginObject(); - ct.Next(TX("a"), guid.a); - ct.Next(TX("b"), guid.b); - ct.Next(TX("c"), guid.c); - ct.Next(TX("d"), guid.d); + ct.Next("a", guid.a); + ct.Next("b", guid.b); + ct.Next("c", guid.c); + ct.Next("d", guid.d); } void Read(Reader& ct, TColor& color) { - ct.BeginObject(); - ct.Next("r", color.r); - ct.Next("g", color.g); - ct.Next("b", color.b); - ct.Next("a", color.a); + u32 value; + ct.Serialize(value); + color = TColor::FromHexAlpha(value); } void Write(Writer& ct, const TColor& color) { - ct.BeginObject(); - ct.Next("r", color.r); - ct.Next("g", color.g); - ct.Next("b", color.b); - ct.Next("a", color.a); + ct.Serialize(color.ToPackedRGBA()); } void Read(Reader& ct, TColor& color) { @@ -1365,96 +1356,96 @@ namespace p void Read(Reader& ct, Vec<2, float>& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); + ct.Next("x", val.x); + ct.Next("y", val.y); } void Write(Writer& ct, const Vec<2, float>& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); + ct.Next("x", val.x); + ct.Next("y", val.y); } void Read(Reader& ct, Vec<2, u32>& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); + ct.Next("x", val.x); + ct.Next("y", val.y); } void Write(Writer& ct, const Vec<2, u32>& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); + ct.Next("x", val.x); + ct.Next("y", val.y); } void Read(Reader& ct, Vec<2, i32>& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); + ct.Next("x", val.x); + ct.Next("y", val.y); } void Write(Writer& ct, const Vec<2, i32>& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); + ct.Next("x", val.x); + ct.Next("y", val.y); } void Read(Reader& ct, Vec<3, float>& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); - ct.Next(TX("z"), val.z); + ct.Next("x", val.x); + ct.Next("y", val.y); + ct.Next("z", val.z); } void Write(Writer& ct, const Vec<3, float>& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); - ct.Next(TX("z"), val.z); + ct.Next("x", val.x); + ct.Next("y", val.y); + ct.Next("z", val.z); } void Read(Reader& ct, Vec<3, u32>& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); - ct.Next(TX("z"), val.z); + ct.Next("x", val.x); + ct.Next("y", val.y); + ct.Next("z", val.z); } void Write(Writer& ct, const Vec<3, u32>& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); - ct.Next(TX("z"), val.z); + ct.Next("x", val.x); + ct.Next("y", val.y); + ct.Next("z", val.z); } void Read(Reader& ct, Vec<3, i32>& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); - ct.Next(TX("z"), val.z); + ct.Next("x", val.x); + ct.Next("y", val.y); + ct.Next("z", val.z); } void Write(Writer& ct, const Vec<3, i32>& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); - ct.Next(TX("z"), val.z); + ct.Next("x", val.x); + ct.Next("y", val.y); + ct.Next("z", val.z); } void Read(Reader& ct, Quat& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); - ct.Next(TX("z"), val.z); - ct.Next(TX("w"), val.w); + ct.Next("x", val.x); + ct.Next("y", val.y); + ct.Next("z", val.z); + ct.Next("w", val.w); } void Write(Writer& ct, const Quat& val) { ct.BeginObject(); - ct.Next(TX("x"), val.x); - ct.Next(TX("y"), val.y); - ct.Next(TX("z"), val.z); - ct.Next(TX("w"), val.w); + ct.Next("x", val.x); + ct.Next("y", val.y); + ct.Next("z", val.z); + ct.Next("w", val.w); } #pragma endregion CoreSupport } // namespace p diff --git a/Src/PipeTime.cpp b/Src/PipeTime.cpp index e04ab4bb..81dc8bad 100644 --- a/Src/PipeTime.cpp +++ b/Src/PipeTime.cpp @@ -7,6 +7,8 @@ #include +// Prevent errors with chrono duration::max() +#undef max namespace p { @@ -20,14 +22,14 @@ namespace p return ToString("%d.%h:%m:%s.%f"); } - String Timespan::ToString(const TChar* format) const + String Timespan::ToString(const char* format) const { String result; ToString(format, result); return result; } - void Timespan::ToString(const TChar* format, String& result) const + void Timespan::ToString(const char* format, String& result) const { result += (*this < Timespan::Zero()) ? '-' : '+'; @@ -126,7 +128,7 @@ namespace p const i32 fractionNano = *Strings::ToI32(Tokens[4]); // Max days - if ((days > Chrono::floor(DecMicroseconds::max()).count() - 1)) + if (days > (Days::max().count() - 1)) { return false; } @@ -306,7 +308,7 @@ namespace p return ToString("%Y.%m.%d-%H.%M.%S"); } - String DateTime::ToString(const TChar* format) const + String DateTime::ToString(const char* format) const { // return Strings::Format(format, *value); String result; @@ -314,7 +316,7 @@ namespace p return result; } - void DateTime::ToString(const TChar* format, String& result) const + void DateTime::ToString(const char* format, String& result) const { if (format) { @@ -423,13 +425,13 @@ namespace p return true; } - bool DateTime::ParseIso8601(const TChar* DateTimeString, DateTime& OutDateTime) + bool DateTime::ParseIso8601(const char* DateTimeString, DateTime& OutDateTime) { // DateOnly: YYYY-MM-DD // DateTime: YYYY-mm-ddTHH:MM:SS(.ssss)(Z|+th:tm|-th:tm) - const TChar* ptr = DateTimeString; - TChar* Next = nullptr; + const char* ptr = DateTimeString; + char* Next = nullptr; i32 Year = 0, Month = 0, Day = 0; i32 Hour = 0, Minute = 0, Second = 0, Millisecond = 0; diff --git a/Src/PipeVectors.cpp b/Src/PipeVectors.cpp index a3ae10d6..e653e0a4 100644 --- a/Src/PipeVectors.cpp +++ b/Src/PipeVectors.cpp @@ -2,7 +2,7 @@ #include "PipeVectors.h" -#include "Pipe/Core/PlatformMisc.h" +#include "PipePlatform.h" namespace p @@ -36,19 +36,31 @@ namespace p { v3 safeReciprocalScale; if (Abs(scale.x) <= tolerance) + { safeReciprocalScale.x = 0.f; + } else + { safeReciprocalScale.x = 1 / scale.x; + } if (Abs(scale.y) <= tolerance) + { safeReciprocalScale.y = 0.f; + } else + { safeReciprocalScale.y = 1 / scale.y; + } if (Abs(scale.z) <= tolerance) + { safeReciprocalScale.z = 0.f; + } else + { safeReciprocalScale.z = 1 / scale.z; + } return safeReciprocalScale; } @@ -200,14 +212,6 @@ namespace p Quat RotationQuat{CR * CP * CY + SR * SP * SY, CR * SP * SY - SR * CP * CY, -CR * SP * CY - SR * CP * SY, CR * CP * SY - SR * SP * CY}; - -#if ENABLE_NAN_DIAGNOSTIC || DO_CHECK - // Very large inputs can cause NaN's. Want to catch this here - ensureMsgf(!RotationQuat.ContainsNaN(), - TEXT("Invalid input to FRotator::Quaternion - generated NaN output: %s"), - *RotationQuat.ToString()); -#endif - return RotationQuat; } diff --git a/Tests/Core/OwnPtr.spec.cpp b/Tests/Core/OwnPtr.spec.cpp index bf96c45d..5d86d59c 100644 --- a/Tests/Core/OwnPtr.spec.cpp +++ b/Tests/Core/OwnPtr.spec.cpp @@ -110,7 +110,7 @@ go_bandit([]() { it("Can copy from other weak", [&]() { TOwnPtr owner = MakeOwned(); - auto* raw = owner.GetUnsafe(); + auto* raw = owner.Get(); TPtr ptr = owner; TPtr ptr2 = ptr; @@ -121,7 +121,7 @@ go_bandit([]() { it("Can move from other weak", [&]() { TOwnPtr owner = MakeOwned(); - auto* raw = owner.GetUnsafe(); + auto* raw = owner.Get(); auto weak = owner.AsPtr(); auto movedWeak = Move(weak); @@ -137,10 +137,10 @@ go_bandit([]() { TPtr ptr = owner; owner.Delete(); - AssertThat(ptr.GetUnsafe(), Is().Not().EqualTo(nullptr)); + AssertThat(ptr.Get(), Is().Not().EqualTo(nullptr)); AssertThat(ptr.IsValid(), Equals(false)); - AssertThat(ptr.GetUnsafe(), Equals(nullptr)); + AssertThat(ptr.Get(), Equals(nullptr)); }); }); diff --git a/Tests/Core/PageBuffer.spec.cpp b/Tests/Core/PageBuffer.spec.cpp index 74ffccb5..092c9e61 100644 --- a/Tests/Core/PageBuffer.spec.cpp +++ b/Tests/Core/PageBuffer.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2024 Piperift - All rights reserved -#include "Pipe/Memory/Alloc.h" +#include "PipeMemory.h" #include #include diff --git a/Tests/ECS/IdRegistry.spec.cpp b/Tests/ECS/IdRegistry.spec.cpp index 8b8784ba..f9a98592 100644 --- a/Tests/ECS/IdRegistry.spec.cpp +++ b/Tests/ECS/IdRegistry.spec.cpp @@ -50,7 +50,7 @@ go_bandit([]() { IdRegistry ids; Id id1 = ids.Create(); - Id id2 = ids.Create(); + ids.Create(); AssertThat(ids.Destroy(id1), Is().True()); AssertThat(ids.IsValid(id1), Is().False()); AssertThat(ids.Size(), Equals(1)); @@ -59,7 +59,7 @@ go_bandit([]() { it("Can create two and remove last", [&]() { IdRegistry ids; - Id id1 = ids.Create(); + ids.Create(); Id id2 = ids.Create(); AssertThat(ids.Destroy(id2), Is().True()); diff --git a/Tests/Math/Math.spec.cpp b/Tests/Math/Math.spec.cpp index e64df200..a88fc20f 100644 --- a/Tests/Math/Math.spec.cpp +++ b/Tests/Math/Math.spec.cpp @@ -2,13 +2,14 @@ #include #include -#include #include #include +#include #include + using namespace snowhouse; using namespace bandit; using namespace p; diff --git a/Tests/Memory/BestFitArena.spec.cpp b/Tests/Memory/BestFitArena.spec.cpp index 5f5157ef..d21415ad 100644 --- a/Tests/Memory/BestFitArena.spec.cpp +++ b/Tests/Memory/BestFitArena.spec.cpp @@ -1,8 +1,7 @@ // Copyright 2015-2024 Piperift - All rights reserved #include -#include -#include +#include using namespace snowhouse; diff --git a/Tests/Memory/BigBestFitArena.spec.cpp b/Tests/Memory/BigBestFitArena.spec.cpp index 3f738605..16487771 100644 --- a/Tests/Memory/BigBestFitArena.spec.cpp +++ b/Tests/Memory/BigBestFitArena.spec.cpp @@ -1,8 +1,7 @@ // Copyright 2015-2024 Piperift - All rights reserved #include -#include -#include +#include using namespace snowhouse; @@ -16,7 +15,6 @@ struct TypeOfSize p::u8 data[size]{0}; // Fill data for debugging }; - go_bandit([]() { describe("Memory.BigBestFitArena", []() { it("Reserves a block on construction", [&]() { diff --git a/Tests/Memory/Memory.spec.cpp b/Tests/Memory/Memory.spec.cpp index 3177f281..8bc3554f 100644 --- a/Tests/Memory/Memory.spec.cpp +++ b/Tests/Memory/Memory.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2024 Piperift - All rights reserved #include -#include +#include using namespace snowhouse; diff --git a/Tests/Memory/MonoLinearArena.spec.cpp b/Tests/Memory/MonoLinearArena.spec.cpp index 2373c463..475f68c2 100644 --- a/Tests/Memory/MonoLinearArena.spec.cpp +++ b/Tests/Memory/MonoLinearArena.spec.cpp @@ -1,8 +1,7 @@ // Copyright 2015-2024 Piperift - All rights reserved #include -#include -#include +#include using namespace snowhouse; @@ -41,7 +40,7 @@ go_bandit([]() { MonoLinearArena arena{1024}; arena.Release(); - TArray blocks; + TArray blocks; arena.GetBlocks(blocks); AssertThat(blocks.Size(), Equals(1)); }); @@ -96,7 +95,7 @@ go_bandit([]() { it("Allocates at correct addresses", [&]() { MonoLinearArena arena{1024}; - TArray blocks; + TArray blocks; arena.GetBlocks(blocks); void* p1 = arena.Alloc(sizeof(float)); @@ -118,7 +117,7 @@ go_bandit([]() { AssertThat(arena.GetAvailableMemory(), Is().EqualTo(16)); void* p3 = arena.Alloc(sizeof(float*)); // 8 bytes - TArray blocks; + TArray blocks; arena.GetBlocks(blocks); AssertThat(blocks.Size(), Equals(2)); AssertThat(blocks[0], Is().Not().EqualTo(blocks[1])); diff --git a/Tests/Reflection/Object.spec.cpp b/Tests/Reflection/Object.spec.cpp index beae7764..943a8947 100644 --- a/Tests/Reflection/Object.spec.cpp +++ b/Tests/Reflection/Object.spec.cpp @@ -6,15 +6,14 @@ using namespace snowhouse; using namespace bandit; -using namespace p; -class TestObject : public Object +class TestObject : public p::Object { - using Super = Object; +public: + using Super = p::Object; P_CLASS(TestObject); -public: bool bConstructed = false; TestObject() @@ -28,15 +27,15 @@ go_bandit([]() { describe("Reflection.Object", []() { describe("Pointers", []() { it("Can create object", [&]() { - auto owner = MakeOwned(); + auto owner = p::MakeOwned(); AssertThat(owner.Get(), Is().Not().EqualTo(nullptr)); AssertThat(owner->bConstructed, Equals(true)); }); it("Can create object with owner", [&]() { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(owner); + auto owner = p::MakeOwned(); + auto owner2 = p::MakeOwned(owner); AssertThat(owner2->bConstructed, Equals(true)); AssertThat(owner2->GetOwner().IsValid(), Equals(true)); diff --git a/Tests/Reflection/Traits.spec.cpp b/Tests/Reflection/Traits.spec.cpp index 9ce97470..5a619071 100644 --- a/Tests/Reflection/Traits.spec.cpp +++ b/Tests/Reflection/Traits.spec.cpp @@ -1,7 +1,9 @@ // Copyright 2015-2024 Piperift - All rights reserved #include +#include #include +#include using namespace snowhouse; diff --git a/Tests/Reflection/TypeName.spec.cpp b/Tests/Reflection/TypeName.spec.cpp index 32112ea6..470f3c23 100644 --- a/Tests/Reflection/TypeName.spec.cpp +++ b/Tests/Reflection/TypeName.spec.cpp @@ -9,7 +9,6 @@ #include - using namespace snowhouse; using namespace bandit; using namespace p; @@ -39,7 +38,7 @@ go_bandit([]() { AssertThat(GetTypeName(), Equals("i16")); AssertThat(GetTypeName(), Equals("i32")); AssertThat(GetTypeName(), Equals("i64")); - AssertThat(GetTypeName(), Equals("TChar")); + AssertThat(GetTypeName(), Equals("char")); AssertThat(GetTypeName(), Equals("StringView")); AssertThat(GetTypeName(), Equals("String")); }); diff --git a/Tests/main.cpp b/Tests/main.cpp index b82ee4bf..1418a870 100644 --- a/Tests/main.cpp +++ b/Tests/main.cpp @@ -1,11 +1,10 @@ // Copyright 2015-2024 Piperift - All rights reserved -#include +#include // Override as first include #include #include -#include // namespace backward