Compare commits

...

27 commits

Author SHA1 Message Date
0c4367cd8a
update installation instructions, usage 2024-06-17 21:09:27 +02:00
a1c3b3f8ef
update README 2024-06-17 20:55:24 +02:00
1a2824453f
improve CLI documentation 2024-06-14 11:20:13 +02:00
e602f2a41a
Add check for draw pile size 2024-06-14 11:18:24 +02:00
9c1cb5ff4e
update windows installation 2024-06-12 17:52:44 +02:00
1d01c7012c
Remove unit tests from main branch 2024-06-10 13:08:05 +02:00
2388c57d5e
bugfix: parse score goal
Boost parses uint8_t as chars, not as numbers,
so 'unsigned int' is used for parsing instead.
2024-06-05 17:34:45 +02:00
90f27bb26d
Fix bug when reverting to turn 1 in recursive mode 2024-03-17 11:16:04 +01:00
b966475045
Fix: Allow user to strikeout 2024-03-15 14:57:38 +01:00
60c405fa20
Add option to list all actions, even unreasonable ones 2024-03-05 22:51:54 +01:00
fd4f080d07
Add shell option to auto-evaluate all game actions 2024-03-05 22:46:51 +01:00
9af42a43a4
reduce number of map lookups 2024-02-10 00:27:00 +01:00
3dafee21dd
fix interactive option 2024-02-09 16:39:02 +01:00
5c4a2bb4f7
Store rationals without denominator
Instead of storing a rational for evey game state,
we just store how many of the factorial(draw pile size) many
game states can be won.
This allows us to save only one 64-bit integer per game state instead of
two and thus reduces memory consumption of the program significantly.
Also, this makes some computations a bit easier, since we do not have to
normalize when recursing - we can just add the numbe of winnable states
for each possible draw.

On the other hand, this means that upon lookup, we have to normalize the
stored values again to retrieve the probabilities.
In particular, one needs to know what the draw pile size of the game
state is in order to interpret the value of the state.
2024-02-09 15:58:15 +01:00
afb6fee540
intoduce CLI option to reduce memory consumption 2024-02-08 22:08:44 +01:00
bfc731ae36
Introduce option for HanabiState to save memory 2024-02-08 21:54:39 +01:00
27922de8e8
introduce function to select which states to save to map 2024-02-08 21:44:57 +01:00
f0a496a8f0
Use parallel_hashmap library for better performance
This drastically reduces memory usage and also gains some performance.
Since this is a drop-in replacement, there is essentially no downside in
using this.
2024-02-07 19:52:02 +01:00
5507f8e5dc
Introduce using directive for map type
This sets up for quick testing of different maps with the same interface
2024-02-07 18:59:08 +01:00
3985ffaac4
fix type 2024-02-06 21:06:58 +01:00
80290a85f9
add missing files for version generation 2024-02-05 22:28:15 +01:00
9d1d27d65c
deactivate debug macro 2024-02-05 22:20:40 +01:00
7699b20498
CMake: Fix version generation: regenare on every build 2024-02-05 19:17:26 +01:00
742266fe82
Include version information into program 2024-02-03 13:21:58 +01:00
62aabe17f5
fix sort order 2024-02-01 22:26:24 +01:00
47d59464cd
backtracking: consider striking at 8 clues 2024-02-01 22:26:08 +01:00
863baf3acd
consider playing trash at 8 clues 2024-01-17 18:22:20 +01:00
31 changed files with 17698 additions and 216 deletions

View file

@ -1,6 +1,7 @@
cmake_minimum_required(VERSION 3.16)
project(endgame-analyzer CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Werror")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
@ -14,44 +15,47 @@ include(FetchContent)
FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git
GIT_TAG 2553fc41450301cd09a9271c8d2c3e0cf3546b73)
FetchContent_MakeAvailable(cpr)
find_package(Boost 1.75 COMPONENTS program_options unit_test_framework REQUIRED)
find_package(Boost 1.75 COMPONENTS program_options REQUIRED)
set(Boost_USE_STATIC_LIBS ON)
include_directories(.)
include_directories(${Boost_INCLUDE_DIR})
add_executable(endgame-analyzer
src/main.cpp
src/state_explorer.cpp
src/download.cpp
src/command_line_interface.cpp
src/parse_game.cpp
src/hanabi_types.cpp
src/game_interface.cpp
src/make_state.cpp
set(SOURCES
src/state_explorer.cpp
src/download.cpp
src/command_line_interface.cpp
src/parse_game.cpp
src/hanabi_types.cpp
src/game_interface.cpp
src/make_state.cpp
)
add_executable(unit-tests
test/unit_test.cpp
src/state_explorer.cpp
src/download.cpp
src/command_line_interface.cpp
src/parse_game.cpp
src/hanabi_types.cpp
src/game_interface.cpp
src/make_state.cpp
# Include git module from https://github.com/rpavlik/cmake-modules to
# dynamically include git revisions into the build.
# This re-generates the file version.cpp whenever the git HEAD changes.
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules/")
include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
git_describe(GIT_DESCRIPTION)
git_local_changes(GIT_LOCAL_CHANGES)
configure_file(
${CMAKE_SOURCE_DIR}/src/version.cpp.in
${CMAKE_CURRENT_BINARY_DIR}/generated/version.cpp
)
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/generated/version.cpp)
add_executable(endgame-analyzer
src/main.cpp
${SOURCES}
)
target_link_libraries(endgame-analyzer cpr)
target_link_libraries(endgame-analyzer Boost::program_options)
target_link_libraries(endgame-analyzer readline)
target_link_libraries(unit-tests cpr)
target_link_libraries(unit-tests Boost::program_options)
target_link_libraries(unit-tests readline)
target_link_libraries(unit-tests ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})
#set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_GLIBCXX_DEBUG")
# This enables assertions in the RelWithDebInfo build type (assuming gcc or clang)
string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")

View file

@ -32,50 +32,64 @@ I can't provide information for all distributions, but in general it should be l
**Linux systems**:
- `boost` and `cmake`: should be available as a system package via your package manager.
- `readline` should be installed already, otherwise try your package manager as well.
- `cpr` is a bit more complicated:
- On Arch, there is a package in the [AUR](https://aur.archlinux.org/packages/cpr).
- On Fedora, there is also a package via [rpm](https://src.fedoraproject.org/rpms/cpr).
- There might be packages for other distributions, check out the [CPR project on Github](https://github.com/libcpr/cpr#packages-for-linux-distributions).
- If there is no package available for your distribution, see [Installing cpr as a local CMake dependency](# Installing cpr as a local CMake dependency).
- Unfortunately, on many Debian-derivatives, only boost 1.71 is available with your system repositories. To compile this project, you need boost>=1.75.
- As an alternative, you can compile boost from source, refer to the [Boost Unix installation guide][boost-installation]. You will need to link against boost binary libraries as well (see section 5), specifically the 'Program Options' (and optionally 'Unit Test' if you want to compile them). For this, you can use `./bootstrap.sh --with-libraries=program_options` (or `--with-libraries=program_options,test`) to limit compilation of the boost libraries to only those you need. To install boost globally, run `sudo ./b2 install`, otherwise you will need to point CMake to your local installation and edit `CMakeLists.txt` acoordingly.
- `readline` should be installed already, otherwise try your package manager as well. On some systems, you will need to additionally install `libreadline-dev` (or similar), since this includes the development headers needed for linking during compilation.
- `cpr` should be fetched as a submodule from its GitHub repository automatically by CMake.
- Alternatively, install cpr globally via your package manager, see [Installing cpr via your system package manager][#Installing-cpr-via-your-system-package-manager].
**Mac OS**:
- I recommend installing packages with [Homebrew](https://brew.sh).
- So `brew install boost cpr readline cmake` should do the job.
- Note this is not tested, since I do not have access to a Mac.
**Windows**:
- Currently, I have no idea regarding windows, sorry for that.
- I recommend using the 'Windows Subsystem for Linux (WSL)'. Then, follow the Linux system installation from above.
- WSL is the (currently) only tested installation method for Windows.
### Installing cpr as a local CMake dependency
- There is also the option to install `cpr` directly as a dependency through `CMake`.
For this, in `CMakeLists.txt`, replace the line
```
find_package(cpr)
```
with the lines
### Installing cpr via your system package manager
By default, `cpr` is fetched by CMake during installation from its github origin.
You can find details on this installation method on the [cpr github page](https://github.com/lipcpr/cpr) as well.
Alternatively, you might want to install `cpr` globally on your system.
For example, this is possible for Arch via [AUR](https://aur.archlinux.org/packages/cpr) and Fedora via [rpm](https://src.fedoraproject.org/rpms/cpr)
In this case, replace
```
include(FetchContent)
FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git
GIT_TAG 2553fc41450301cd09a9271c8d2c3e0cf3546b73)
FetchContent_MakeAvailable(cpr)
```
You might need to replace the `GIT_TAG` with the latest release tag from [Github](https://github.com/libcpr/cpr/releases),
but the above should usually work.
You can find details on this installation method on the [cpr github page](https://github.com/lipcpr/cpr) as well.
with
```
find_package(cpr)
```
in `CMakeLists.txt`.
## Usage
For a detailed usage explanation, run `./endgame-analyzer --help`.
The general usage is as follows:
```
# ./endgame-analyzer (GAME_ID | GAME_FILE) TURN
# ./endgame-analyzer (-g GAME_ID | -f GAME_FILE) (-t TURN | -d DRAW_PILE_SIZE) [OPTIONS]
```
where
- `GAME_ID` is a game from hanab.live.
- `GAME_FILE` is a path to a file containing the game as JSON in the hanab.live format.
- `TURN` specifies the turn of the game state to analyze. Turn 1 is the state before actions have been taken.
- `OPTIONS` is a list of further options to modify the running of the program, for example
- `--score-goal` to optimize for achieving a certain score not necessarily maximum.
- `--clue-modifier` to edit the base state and apply a relative modifier to the initial clue count
- `--save-memory` Reduce memory usage at the cost of execution time. This typically means 50% less memory, but a slowdown of 4-6 in runtime. You will likely only need this for extreme cases.
- `--version` Print version and compilation information for your program.
- `--recursive` Evaluates the game from the back until specified game state and prints information during evaluation. Also useful for infinite analysis, in case you are unsure which initial game state is solvable in feasible time.
Be cautious about specifying too low turn counts, your program will eventually run out of memory.
Typically, turn counts where roughly 8 cards are still in the draw pile are reasonably fast,
Typically, turn counts where roughly 8-10 cards are still in the draw pile are reasonably fast,
but running times depend heavily on the exact game state you want to analyze.
## License
This is GPLv3-licensed. See [LICENSE](./LICENSE) for details.
[boost-installation]: https://www.boost.org/doc/libs/1_85_0/more/getting_started/unix-variants.html

View file

@ -0,0 +1,287 @@
# - Returns a version string from Git
#
# These functions force a re-configure on each git commit so that you can
# trust the values of the variables in your build system.
#
# get_git_head_revision(<refspecvar> <hashvar> [ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR])
#
# Returns the refspec and sha hash of the current head revision
#
# git_describe(<var> [<additional arguments to git describe> ...])
#
# Returns the results of git describe on the source tree, and adjusting
# the output so that it tests false if an error occurs.
#
# git_describe_working_tree(<var> [<additional arguments to git describe> ...])
#
# Returns the results of git describe on the working tree (--dirty option),
# and adjusting the output so that it tests false if an error occurs.
#
# git_get_exact_tag(<var> [<additional arguments to git describe> ...])
#
# Returns the results of git describe --exact-match on the source tree,
# and adjusting the output so that it tests false if there was no exact
# matching tag.
#
# git_local_changes(<var>)
#
# Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes.
# Uses the return code of "git diff-index --quiet HEAD --".
# Does not regard untracked files.
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2020 Rylie Pavlik <rylie@ryliepavlik.com>
# https://ryliepavlik.com/
#
# Copyright 2009-2013, Iowa State University.
# Copyright 2013-2020, Rylie Pavlik
# Copyright 2013-2020, Contributors
#
# SPDX-License-Identifier: BSL-1.0
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if(__get_git_revision_description)
return()
endif()
set(__get_git_revision_description YES)
# We must run the following at "include" time, not at function call time,
# to find the path to this module rather than the path to a calling list file
get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
# Function _git_find_closest_git_dir finds the next closest .git directory
# that is part of any directory in the path defined by _start_dir.
# The result is returned in the parent scope variable whose name is passed
# as variable _git_dir_var. If no .git directory can be found, the
# function returns an empty string via _git_dir_var.
#
# Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and
# neither foo nor bar contain a file/directory .git. This wil return
# C:/bla/.git
#
function(_git_find_closest_git_dir _start_dir _git_dir_var)
set(cur_dir "${_start_dir}")
set(git_dir "${_start_dir}/.git")
while(NOT EXISTS "${git_dir}")
# .git dir not found, search parent directories
set(git_previous_parent "${cur_dir}")
get_filename_component(cur_dir "${cur_dir}" DIRECTORY)
if(cur_dir STREQUAL git_previous_parent)
# We have reached the root directory, we are not in git
set(${_git_dir_var}
""
PARENT_SCOPE)
return()
endif()
set(git_dir "${cur_dir}/.git")
endwhile()
set(${_git_dir_var}
"${git_dir}"
PARENT_SCOPE)
endfunction()
function(get_git_head_revision _refspecvar _hashvar)
_git_find_closest_git_dir("${CMAKE_CURRENT_SOURCE_DIR}" GIT_DIR)
if("${ARGN}" STREQUAL "ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR")
set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR TRUE)
else()
set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR FALSE)
endif()
if(NOT "${GIT_DIR}" STREQUAL "")
file(RELATIVE_PATH _relative_to_source_dir "${CMAKE_SOURCE_DIR}"
"${GIT_DIR}")
if("${_relative_to_source_dir}" MATCHES "[.][.]"
AND NOT ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR)
# We've gone above the CMake root dir.
set(GIT_DIR "")
endif()
endif()
if("${GIT_DIR}" STREQUAL "")
set(${_refspecvar}
"GITDIR-NOTFOUND"
PARENT_SCOPE)
set(${_hashvar}
"GITDIR-NOTFOUND"
PARENT_SCOPE)
return()
endif()
# Check if the current source dir is a git submodule or a worktree.
# In both cases .git is a file instead of a directory.
#
if(NOT IS_DIRECTORY ${GIT_DIR})
# The following git command will return a non empty string that
# points to the super project working tree if the current
# source dir is inside a git submodule.
# Otherwise the command will return an empty string.
#
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-parse
--show-superproject-working-tree
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT_VARIABLE out
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT "${out}" STREQUAL "")
# If out is empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule
file(READ ${GIT_DIR} submodule)
string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE
${submodule})
string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE)
get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE}
ABSOLUTE)
set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD")
else()
# GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree
file(READ ${GIT_DIR} worktree_ref)
# The .git directory contains a path to the worktree information directory
# inside the parent git repo of the worktree.
#
string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir
${worktree_ref})
string(STRIP ${git_worktree_dir} git_worktree_dir)
_git_find_closest_git_dir("${git_worktree_dir}" GIT_DIR)
set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD")
endif()
else()
set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD")
endif()
set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
if(NOT EXISTS "${GIT_DATA}")
file(MAKE_DIRECTORY "${GIT_DATA}")
endif()
if(NOT EXISTS "${HEAD_SOURCE_FILE}")
return()
endif()
set(HEAD_FILE "${GIT_DATA}/HEAD")
configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY)
configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
"${GIT_DATA}/grabRef.cmake" @ONLY)
include("${GIT_DATA}/grabRef.cmake")
set(${_refspecvar}
"${HEAD_REF}"
PARENT_SCOPE)
set(${_hashvar}
"${HEAD_HASH}"
PARENT_SCOPE)
endfunction()
function(git_describe _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
get_git_head_revision(refspec hash)
if(NOT GIT_FOUND)
set(${_var}
"GIT-NOTFOUND"
PARENT_SCOPE)
return()
endif()
if(NOT hash)
set(${_var}
"HEAD-HASH-NOTFOUND"
PARENT_SCOPE)
return()
endif()
# TODO sanitize
#if((${ARGN}" MATCHES "&&") OR
# (ARGN MATCHES "||") OR
# (ARGN MATCHES "\\;"))
# message("Please report the following error to the project!")
# message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}")
#endif()
#message(STATUS "Arguments to execute_process: ${ARGN}")
execute_process(
COMMAND "${GIT_EXECUTABLE}" describe --tags --always ${hash} ${ARGN}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE res
OUTPUT_VARIABLE out
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(out "${out}-${res}-NOTFOUND")
endif()
set(${_var}
"${out}"
PARENT_SCOPE)
endfunction()
function(git_describe_working_tree _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
if(NOT GIT_FOUND)
set(${_var}
"GIT-NOTFOUND"
PARENT_SCOPE)
return()
endif()
execute_process(
COMMAND "${GIT_EXECUTABLE}" describe --dirty ${ARGN}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE res
OUTPUT_VARIABLE out
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(out "${out}-${res}-NOTFOUND")
endif()
set(${_var}
"${out}"
PARENT_SCOPE)
endfunction()
function(git_get_exact_tag _var)
git_describe(out --exact-match ${ARGN})
set(${_var}
"${out}"
PARENT_SCOPE)
endfunction()
function(git_local_changes _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
get_git_head_revision(refspec hash)
if(NOT GIT_FOUND)
set(${_var}
"GIT-NOTFOUND"
PARENT_SCOPE)
return()
endif()
if(NOT hash)
set(${_var}
"HEAD-HASH-NOTFOUND"
PARENT_SCOPE)
return()
endif()
execute_process(
COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD --
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE res
OUTPUT_VARIABLE out
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
if(res EQUAL 0)
set(${_var}
"CLEAN"
PARENT_SCOPE)
else()
set(${_var}
"DIRTY"
PARENT_SCOPE)
endif()
endfunction()

View file

@ -0,0 +1,48 @@
#
# Internal file for GetGitRevisionDescription.cmake
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2023 Rylie Pavlik <rylie@ryliepavlik.com>
# https://ryliepavlik.com/
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright 2009-2012, Iowa State University
# Copyright 2011-2023, Contributors
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
#
# SPDX-License-Identifier: BSL-1.0
set(HEAD_HASH)
file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
if(HEAD_CONTENTS MATCHES "ref")
# named branch
string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
if(EXISTS "@GIT_DIR@/${HEAD_REF}")
configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
else()
if(EXISTS "@GIT_DIR@/packed-refs")
configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs"
COPYONLY)
file(READ "@GIT_DATA@/packed-refs" PACKED_REFS)
if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}")
set(HEAD_HASH "${CMAKE_MATCH_1}")
endif()
endif()
endif()
else()
# detached HEAD
configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
endif()
if(NOT HEAD_HASH)
file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
string(STRIP "${HEAD_HASH}" HEAD_HASH)
endif()

View file

@ -28,7 +28,7 @@ namespace Hanabi
* to be winning.
* If std::nullopt, then the maximum score of the game will be used.
*/
boost::optional<uint8_t> score_goal{};
boost::optional<unsigned int> score_goal{};
/**
* Whether game_state_spec denotes a turn number or draw pile size.
@ -72,6 +72,19 @@ namespace Hanabi
* and future game states, including suboptimal ones.
*/
bool list_actions{false};
/**
* If set to true, only roughly half of the game states will be stored in the internal lookup table.
* This results in roughly halving memory consumption at the cost of roughly a factor 3-5 in execution speed
* (the slowdown is theoretically bounded to visiting at most 9 times the number of states if this option is activated).
* You should typically never need this, unless you are very specifically short on memory and.
* */
bool save_memory{false};
/**
* If true, prints version information of the program and exits immediately.
*/
bool version_info{false};
};
/**

8
include/deck_generator.h Normal file
View file

@ -0,0 +1,8 @@
//
// Created by maximilian on 3/12/24.
//
#ifndef ENDGAME_ANALYZER_DECK_GENERATOR_H
#define ENDGAME_ANALYZER_DECK_GENERATOR_H
#endif //ENDGAME_ANALYZER_DECK_GENERATOR_H

View file

@ -23,7 +23,7 @@ namespace Download
* @return Game state
*
*/
Hanabi::Game get_game(int game_id, std::optional<uint8_t> score_goal);
Hanabi::Game get_game(int game_id, std::optional<uint8_t> score_goal, bool save_memory = false);
/**
* @brief Create game object from given source.
@ -32,7 +32,7 @@ namespace Download
* @return Game state
*
*/
Hanabi::Game get_game(std::string const & filename, std::optional<uint8_t> score_goal);
Hanabi::Game get_game(std::string const & filename, std::optional<uint8_t> score_goal, bool save_memory = false);
} // namespace Download

42
include/factorial.h Normal file
View file

@ -0,0 +1,42 @@
//
// Created by maximilian on 2/9/24.
//
#ifndef ENDGAME_ANALYZER_FACTORIAL_H
#define ENDGAME_ANALYZER_FACTORIAL_H
#include <cstdint>
namespace Factorial {
inline std::uint64_t factorial(std::size_t n)
{
static const std::uint64_t table[] = {
1,
1 ,
2 ,
6 ,
24 ,
120 ,
720 ,
5040 ,
40320 ,
362880 ,
3628800 ,
39916800 ,
479001600 ,
6227020800 ,
87178291200 ,
1307674368000 ,
20922789888000 ,
355687428096000 ,
6402373705728000 ,
121645100408832000 ,
2432902008176640000 ,
};
assert(n <= 20);
return table[n];
}
}
#endif //ENDGAME_ANALYZER_FACTORIAL_H

View file

@ -4,7 +4,6 @@
#include <cstddef>
#include <iosfwd>
#include <vector>
#include <unordered_map>
#include <memory>
#include "hanabi_types.hpp"
@ -25,6 +24,21 @@ namespace Hanabi
bool operator!=(const CardMultiplicity &) const;
};
struct HanabiStateConfig
{
/** What score to consider as a win. If left empty, automatically replaced by max score on construction. */
std::optional<uint8_t> score_goal;
/** The number of clues gained when a stack is finished or a card is discarded. Usually 1, 1/2 in clue starved. */
clue_t num_clues_gained_on_discard_or_stack_finished {1};
/**
* If set to true, only roughly half of the game states will be stored in the internal lookup table.
* This results in roughly halving memory consumption at the cost of roughly a factor 3-5 in execution speed
* (the slowdown is theoretically bounded to visiting at most 9 times the number of states if this option is activated).
* You should typically never need this, unless you are very specifically short on memory and.
* */
bool save_memory {false};
};
class HanabiStateIF
{
public:
@ -48,6 +62,8 @@ namespace Hanabi
[[nodiscard]] virtual clue_t num_clues() const = 0;
[[nodiscard]] virtual unsigned num_strikes() const = 0;
[[nodiscard]] virtual unsigned score() const = 0;
[[nodiscard]] virtual std::vector<std::vector<Card>> hands() const = 0;
@ -69,7 +85,7 @@ namespace Hanabi
[[nodiscard]] virtual std::uint64_t enumerated_states() const = 0;
[[nodiscard]] virtual const std::unordered_map<unsigned long, probability_t> & position_tablebase() const = 0;
[[nodiscard]] virtual const map_type<unsigned long, std::uint64_t> & position_tablebase() const = 0;
virtual void init_backtracking_information() = 0;

View file

@ -9,7 +9,6 @@
#include <optional>
#include <ostream>
#include <stack>
#include <unordered_map>
#include <vector>
#include <memory>
@ -63,7 +62,6 @@ namespace Hanabi
std::array<inner_array_t, num_suits> _array{};
};
// A game mimics a game state together with a list of actions and allows to traverse the game
// history by making and reverting the stored actions.
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
@ -72,7 +70,7 @@ namespace Hanabi
public:
HanabiState() = default;
explicit HanabiState(const std::vector<Card> & deck, uint8_t score_goal = 5 * num_suits, clue_t num_clues_gained_on_discard_or_stack_finished = 1);
explicit HanabiState(const std::vector<Card> & deck, HanabiStateConfig config = HanabiStateConfig());
void give_clue() final;
@ -94,6 +92,8 @@ namespace Hanabi
[[nodiscard]] clue_t num_clues() const final;
[[nodiscard]] unsigned num_strikes() const final;
[[nodiscard]] unsigned score() const final;
[[nodiscard]] std::vector<std::vector<Card>> hands() const final;
@ -115,7 +115,7 @@ namespace Hanabi
[[nodiscard]] std::uint64_t enumerated_states() const final;
[[nodiscard]] const std::unordered_map<unsigned long, probability_t> & position_tablebase() const final;
[[nodiscard]] const map_type<unsigned long, std::uint64_t> & position_tablebase() const final;
void init_backtracking_information() final;
@ -199,7 +199,7 @@ namespace Hanabi
unsigned long discard_and_potentially_update(hand_index_t index, bool cycle = false);
unsigned long play_and_potentially_update(hand_index_t index, bool cycle = false);
unsigned long play_and_potentially_update(hand_index_t index, bool cycle, bool allow_strikeout);
unsigned draw(hand_index_t index, bool cycle = false, bool played = true);
@ -212,7 +212,7 @@ namespace Hanabi
void revert_play(bool cycle = false);
void update_tablebase(unsigned long id, probability_t probability);
void update_tablebase(unsigned long id, std::uint64_t probability);
template<class Function>
void do_for_each_potential_draw(hand_index_t index, bool play, Function f);
@ -223,7 +223,16 @@ namespace Hanabi
void check_draw_pile_integrity() const;
probability_t check_play_or_discard(hand_index_t index, bool play);
std::uint64_t check_play_or_discard(hand_index_t index, bool play);
// For the current state, returns whether we will save it in the lookup table.
// By default, this is just constant true, but we might want to trade memory for speed, i.e.
// store less states, which will reduce memory consumption at the cost of re-computing some of the values
// when re-visiting the states.
bool save_state_to_map();
std::uint64_t internal_evaluate_state();
[[nodiscard]] std::optional<std::uint64_t> internal_lookup() const;
static constexpr uint8_t no_endgame = std::numeric_limits<uint8_t>::max();
@ -233,6 +242,7 @@ namespace Hanabi
player_t _turn{};
clue_t _num_clues{};
unsigned _num_strikes{};
std::uint8_t _weighted_draw_pile_size{};
Stacks<num_suits> _stacks{};
std::array<std::array<Card, hand_size>, num_players> _hands{};
@ -255,7 +265,8 @@ namespace Hanabi
RelativeRepresentationData _relative_representation;
// Lookup table for states. Uses the ids calculated using the relative representation
std::unordered_map<unsigned long, probability_t> _position_tablebase;
bool const _save_memory;
map_type<unsigned long, std::uint64_t> _position_tablebase;
std::uint64_t _enumerated_states{};
};

View file

@ -14,6 +14,8 @@
#define CHECK_DRAW_PILE_INTEGRITY
#endif
#include "factorial.h"
namespace Hanabi
{
@ -75,11 +77,11 @@ namespace Hanabi
}
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
HanabiState<num_suits, num_players, hand_size>::HanabiState(const std::vector<Card> & deck, uint8_t score_goal, clue_t num_clues_gained_on_discard_or_stack_finished):
_clues_gained_on_discard_or_stack_finished(num_clues_gained_on_discard_or_stack_finished)
, _score_goal(score_goal), _turn(0), _num_clues(max_num_clues), _weighted_draw_pile_size(deck.size()), _stacks(), _hands(), _draw_pile()
, _endgame_turns_left(no_endgame), _pace(deck.size() - score_goal - num_players * (hand_size - 1)), _score(0)
, _actions_log(), _relative_representation(), _position_tablebase()
HanabiState<num_suits, num_players, hand_size>::HanabiState(const std::vector<Card> & deck, HanabiStateConfig config):
_clues_gained_on_discard_or_stack_finished(config.num_clues_gained_on_discard_or_stack_finished)
, _score_goal(config.score_goal.value_or(num_suits * 5)), _turn(0), _num_clues(max_num_clues), _num_strikes(0), _weighted_draw_pile_size(deck.size()), _stacks(), _hands(), _draw_pile()
, _endgame_turns_left(no_endgame), _pace(deck.size() - _score_goal - num_players * (hand_size - 1)), _score(0)
, _actions_log(), _relative_representation(), _save_memory(config.save_memory), _position_tablebase()
, _enumerated_states(0)
{
std::fill(_stacks.begin(), _stacks.end(), starting_card_rank);
@ -189,12 +191,12 @@ namespace Hanabi
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
void HanabiState<num_suits, num_players, hand_size>::play(Hanabi::hand_index_t index)
{
play_and_potentially_update(index);
play_and_potentially_update(index, false, true);
}
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
unsigned long
HanabiState<num_suits, num_players, hand_size>::play_and_potentially_update(hand_index_t index, bool cycle)
HanabiState<num_suits, num_players, hand_size>::play_and_potentially_update(hand_index_t index, bool cycle, bool allow_strikeout)
{
CHECK_DRAW_PILE_INTEGRITY;
ASSERT(index < _hands[_turn].size());
@ -214,6 +216,8 @@ namespace Hanabi
_num_clues += _clues_gained_on_discard_or_stack_finished;
}
} else {
_num_strikes++;
ASSERT(_num_strikes <= max_num_strikes or allow_strikeout);
_num_copies_left[played_card]--;
}
@ -271,7 +275,7 @@ namespace Hanabi
void HanabiState<num_suits, num_players, hand_size>::print(std::ostream & os) const
{
os << "Stacks: " << _stacks << " (score " << +_score << ")";
os << ", clues: " << +_num_clues << ", turn: " << +_turn;
os << ", clues: " << +_num_clues << ", strikes: " << +_num_strikes << ", turn: " << +_turn;
if (_endgame_turns_left != no_endgame)
{
os << ", " << +_endgame_turns_left << " turns left";
@ -533,6 +537,12 @@ namespace Hanabi
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
void HanabiState<num_suits, num_players, hand_size>::init_backtracking_information()
{
if (_weighted_draw_pile_size > 20)
{
std::stringstream ss;
ss << "Detected draw pile size of " << +_weighted_draw_pile_size << " is too big, can at most be 20.";
throw std::runtime_error(ss.str());
}
ASSERT(not _relative_representation.initialized);
// Note that this function does not have to be particularly performant, we only call it once to initialize.
const Card trash = [this]() -> Card {
@ -644,6 +654,8 @@ namespace Hanabi
} else {
// If we misplayed, then we lost the card and have to regain it now
_num_copies_left[last_action.discarded]++;
_num_strikes--;
assert(_num_strikes >= 0);
}
CHECK_DRAW_PILE_INTEGRITY;
}
@ -738,6 +750,12 @@ namespace Hanabi
return _num_clues;
}
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
unsigned HanabiState<num_suits, num_players, hand_size>::num_strikes() const
{
return _num_strikes;
}
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
unsigned HanabiState<num_suits, num_players, hand_size>::score() const
{
@ -794,7 +812,7 @@ namespace Hanabi
{
std::vector<std::pair<Action, std::optional<probability_t>>> actions{};
if (_score == _score_goal or _pace < 0 or _endgame_turns_left == 0)
if (_score == _score_goal or _pace < 0 or _endgame_turns_left == 0 or _num_strikes > max_num_strikes)
{
return actions;
}
@ -805,7 +823,7 @@ namespace Hanabi
for (std::uint8_t index = 0; index < hand_size; index++)
{
Card card = hand[index];
bool const consider_playing = is_playable(card) or (not is_critical(card) and not reasonable and (not is_trash(card) or not played_trash));
bool const consider_playing = is_playable(card) or (_num_strikes < max_num_strikes and not is_critical(card) and (not reasonable or _num_clues == max_num_clues) and (not is_trash(card) or not played_trash));
if (consider_playing)
{
if (is_trash(card))
@ -932,10 +950,24 @@ namespace Hanabi
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
std::optional<probability_t> HanabiState<num_suits, num_players, hand_size>::lookup() const
{
std::optional<uint64_t> res = internal_lookup();
if (res.has_value())
{
return probability_t(res.value()) / Factorial::factorial(_weighted_draw_pile_size);
}
else
{
return std::nullopt;
}
}
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
std::optional<std::uint64_t> HanabiState<num_suits, num_players, hand_size>::internal_lookup() const
{
if (_score == 5 * num_suits)
{
return 1;
return Factorial::factorial(_weighted_draw_pile_size);
}
if (_pace < 0 or _endgame_turns_left == 0)
{
@ -972,47 +1004,67 @@ namespace Hanabi
return _actions_log.top().action_type;
}
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
bool HanabiState<num_suits, num_players, hand_size>::save_state_to_map()
{
if (_save_memory)
{
return _weighted_draw_pile_size % 2 == 0;
}
else
{
return true;
}
}
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
probability_t HanabiState<num_suits, num_players, hand_size>::evaluate_state()
{
std::uint64_t num_wins = internal_evaluate_state();
return probability_t(num_wins)/ Factorial::factorial(_weighted_draw_pile_size);
}
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
std::uint64_t HanabiState<num_suits, num_players, hand_size>::internal_evaluate_state()
{
ASSERT(_relative_representation.initialized);
_enumerated_states++;
const unsigned long id_of_state = unique_id();
const unsigned id = 55032;
if (id_of_state == id)
{
std::cout << "Found state with id of " << id << "\n" << *this << std::endl;
}
if (_score == _score_goal)
{
return 1;
return Factorial::factorial(_weighted_draw_pile_size);
}
if (_pace < 0 || _endgame_turns_left == 0)
if (_pace < 0 || _endgame_turns_left == 0 || _num_strikes > max_num_strikes)
{
return 0;
}
#ifndef GAME_STATE_NO_TABLEBASE_LOOKUP
if (_position_tablebase.count(id_of_state) == 1) {
return _position_tablebase[id_of_state];
auto lookup_it = _position_tablebase.find(id_of_state);
if (lookup_it != _position_tablebase.end())
{
return lookup_it->second;
}
#endif
// TODO: Have some endgame analysis here?
probability_t best_probability = 0;
std::uint64_t best_probability = 0;
const std::array<Card, hand_size> & hand = _hands[_turn];
// First, check for playables
bool played_trash = false;
for (std::uint8_t index = 0; index < hand_size; index++)
{
if (is_playable(hand[index]))
if (is_playable(hand[index]) or (_num_clues == max_num_clues and _num_strikes < max_num_strikes and not is_critical(hand[index]) and (not is_trash(hand[index]) or not played_trash)))
{
probability_t const probability_play = check_play_or_discard(index, true);
if (is_trash(hand[index])) {
played_trash = true;
}
std::uint64_t const probability_play = check_play_or_discard(index, true);
best_probability = std::max(best_probability, probability_play);
if (best_probability == 1)
if (best_probability == Factorial::factorial(_weighted_draw_pile_size))
{
update_tablebase(id_of_state, best_probability);
return best_probability;
@ -1021,8 +1073,9 @@ namespace Hanabi
}
// Check for discards now
if (_pace > 0 and _num_clues < max_num_clues)
if (_pace > 0 and (_num_clues < max_num_clues or _num_strikes < max_num_strikes))
{
bool const play_card_instead_of_discarding = _num_clues == max_num_clues;
// This will hold the index of trash to discard
std::uint8_t const invalid_index = std::numeric_limits<std::uint8_t>::max();
std::uint8_t discard_index = invalid_index;
@ -1042,7 +1095,7 @@ namespace Hanabi
if (discard_index == invalid_index) {
for (std::uint8_t index = 0; index < hand_size; index++) {
Card const card = _hands[_turn][index];
auto it = std::find_if(_hands[_turn].begin() + index + 1, _hands[_turn].end(), [&card, this](Card const & card_in_hand) {
auto it = std::find_if(_hands[_turn].begin() + index + 1, _hands[_turn].end(), [&card](Card const & card_in_hand) {
return card_in_hand == card;
});
if (it != _hands[_turn].end()) {
@ -1056,10 +1109,10 @@ namespace Hanabi
// Discard if we found trash now
if (discard_index != invalid_index) {
probability_t const probability_discard = check_play_or_discard(discard_index, false);
std::uint64_t const probability_discard = check_play_or_discard(discard_index, play_card_instead_of_discarding);
best_probability = std::max(best_probability, probability_discard);
if (best_probability == 1)
if (best_probability == Factorial::factorial(_weighted_draw_pile_size))
{
update_tablebase(id_of_state, best_probability);
return best_probability;
@ -1069,10 +1122,10 @@ namespace Hanabi
// sacrifice cards in hand
for(hand_index_t index = 0; index < hand_size; ++index) {
if(!is_critical(hand[index])) {
probability_t const probability_sacrifice = check_play_or_discard(index, false);
std::uint64_t const probability_sacrifice = check_play_or_discard(index, play_card_instead_of_discarding);
best_probability = std::max(best_probability, probability_sacrifice);
if (best_probability == 1)
if (best_probability == Factorial::factorial(_weighted_draw_pile_size))
{
update_tablebase(id_of_state, best_probability);
return best_probability;
@ -1086,14 +1139,9 @@ namespace Hanabi
if (_num_clues >= clue_t(1))
{
give_clue();
const probability_t probability_stall = evaluate_state();
const std::uint64_t probability_stall = internal_evaluate_state();
revert_clue();
best_probability = std::max(best_probability, probability_stall);
if (best_probability == 1)
{
update_tablebase(id_of_state, best_probability);
return best_probability;
};
}
update_tablebase(id_of_state, best_probability);
@ -1101,15 +1149,14 @@ namespace Hanabi
}
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
probability_t HanabiState<num_suits, num_players, hand_size>::check_play_or_discard(hand_index_t index, bool play) {
probability_t sum_of_probabilities = 0;
std::uint64_t HanabiState<num_suits, num_players, hand_size>::check_play_or_discard(hand_index_t index, bool play) {
std::uint64_t sum_of_probabilities = 0;
do_for_each_potential_draw(index, play, [this, &sum_of_probabilities](const unsigned long multiplicity) {
sum_of_probabilities += evaluate_state() * multiplicity;
sum_of_probabilities += internal_evaluate_state() * multiplicity;
});
const unsigned long total_weight = std::max(static_cast<unsigned long>(_weighted_draw_pile_size), 1ul);
return sum_of_probabilities / total_weight;
return sum_of_probabilities;
}
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
@ -1121,7 +1168,7 @@ namespace Hanabi
auto do_action = [this, index, play]() {
if (play)
{
return play_and_potentially_update(index, true);
return play_and_potentially_update(index, true, false);
}
else
{
@ -1164,7 +1211,8 @@ namespace Hanabi
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
std::uint64_t HanabiState<num_suits, num_players, hand_size>::unique_id() const
{
unsigned long id = 0;
// Encode strikes first, since they will often be zero.
unsigned long id = _num_strikes;
// encode all positions of cards that started in draw pile
ASSERT(_relative_representation.card_positions_draw.size() == _relative_representation.good_cards_draw.size());
@ -1247,6 +1295,9 @@ namespace Hanabi
std::vector<std::uint64_t> ret;
std::vector<Card> cards;
// encode strikes first
ret.push_back(_num_strikes);
// encode all positions of cards that started in draw pile
ASSERT(_relative_representation.card_positions_draw.size() == _relative_representation.good_cards_draw.size());
for (size_t i = 0; i < _relative_representation.card_positions_draw.size(); i++)
@ -1317,7 +1368,7 @@ namespace Hanabi
}
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
const std::unordered_map<unsigned long, probability_t> &
const map_type<unsigned long, std::uint64_t> &
HanabiState<num_suits, num_players, hand_size>::position_tablebase() const
{
return _position_tablebase;
@ -1337,11 +1388,10 @@ namespace Hanabi
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
void HanabiState<num_suits, num_players, hand_size>::update_tablebase(
unsigned long id, Hanabi::probability_t probability
unsigned long id, std::uint64_t probability
)
{
// This macro can be activated if we want to dump details on all game states visited for analysis purposes.
#define DUMP_STATES
#ifdef DUMP_STATES
if (id == 87476369689) {
std::cout << *this << std::endl;
@ -1359,11 +1409,16 @@ namespace Hanabi
std::cout << "\n" << std::endl;
}
#endif
#ifndef NDEBUG
if (_position_tablebase.count(id) == 1)
{
ASSERT(_position_tablebase[id] == probability);
}
_position_tablebase[id] = probability;
#endif
if (save_state_to_map())
{
_position_tablebase[id] = probability;
}
}
} // namespace Hanabi

View file

@ -8,6 +8,9 @@
#include <boost/rational.hpp>
#include <unordered_map>
#include <parallel_hashmap/phmap.h>
namespace Hanabi
{
@ -20,6 +23,9 @@ namespace Hanabi
using probability_base_type = unsigned long;
using rational_probability = boost::rational<probability_base_type>;
template<class Key, class Value>
using map_type = phmap::parallel_flat_hash_map<Key, Value>;
/**
* Define macro
* NUSE_RATIONAL_PROBABILITIES
@ -52,6 +58,7 @@ namespace Hanabi
constexpr suit_t max_suit_index = 5;
constexpr size_t max_card_duplicity = 3;
const clue_t max_num_clues = 8;
constexpr unsigned max_num_strikes = 2; /** Maximum number of allowed strikes */
constexpr hand_index_t invalid_hand_idx = std::numeric_limits<hand_index_t>::max();
// We might want to change these at runtime to adapt to other variants.

View file

@ -26,8 +26,7 @@ namespace Hanabi
std::size_t num_suits,
Hanabi::player_t num_players,
std::vector<Hanabi::Card> const &deck,
clue_t num_clues_gained_on_discard_or_stack_finished = clue_t(1),
std::optional<uint8_t> score_goal = std::nullopt
Hanabi::HanabiStateConfig config
);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,195 @@
#if !defined(spp_memory_h_guard)
#define spp_memory_h_guard
#include <cstdint>
#include <cstring>
#include <cstdlib>
#if defined(_WIN32) || defined( __CYGWIN__)
#define SPP_WIN
#endif
#ifdef SPP_WIN
#include <windows.h>
#include <Psapi.h>
#undef min
#undef max
#elif defined(__linux__)
#include <sys/types.h>
#include <sys/sysinfo.h>
#elif defined(__FreeBSD__)
#include <paths.h>
#include <fcntl.h>
#include <kvm.h>
#include <unistd.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#endif
namespace spp
{
uint64_t GetSystemMemory();
uint64_t GetTotalMemoryUsed();
uint64_t GetProcessMemoryUsed();
uint64_t GetPhysicalMemory();
uint64_t GetSystemMemory()
{
#ifdef SPP_WIN
MEMORYSTATUSEX memInfo;
memInfo.dwLength = sizeof(MEMORYSTATUSEX);
GlobalMemoryStatusEx(&memInfo);
return static_cast<uint64_t>(memInfo.ullTotalPageFile);
#elif defined(__linux__)
struct sysinfo memInfo;
sysinfo (&memInfo);
auto totalVirtualMem = memInfo.totalram;
totalVirtualMem += memInfo.totalswap;
totalVirtualMem *= memInfo.mem_unit;
return static_cast<uint64_t>(totalVirtualMem);
#elif defined(__FreeBSD__)
kvm_t *kd;
u_int pageCnt;
size_t pageCntLen = sizeof(pageCnt);
u_int pageSize;
struct kvm_swap kswap;
uint64_t totalVirtualMem;
pageSize = static_cast<u_int>(getpagesize());
sysctlbyname("vm.stats.vm.v_page_count", &pageCnt, &pageCntLen, NULL, 0);
totalVirtualMem = pageCnt * pageSize;
kd = kvm_open(NULL, _PATH_DEVNULL, NULL, O_RDONLY, "kvm_open");
kvm_getswapinfo(kd, &kswap, 1, 0);
kvm_close(kd);
totalVirtualMem += kswap.ksw_total * pageSize;
return totalVirtualMem;
#else
return 0;
#endif
}
uint64_t GetTotalMemoryUsed()
{
#ifdef SPP_WIN
MEMORYSTATUSEX memInfo;
memInfo.dwLength = sizeof(MEMORYSTATUSEX);
GlobalMemoryStatusEx(&memInfo);
return static_cast<uint64_t>(memInfo.ullTotalPageFile - memInfo.ullAvailPageFile);
#elif defined(__linux__)
struct sysinfo memInfo;
sysinfo(&memInfo);
auto virtualMemUsed = memInfo.totalram - memInfo.freeram;
virtualMemUsed += memInfo.totalswap - memInfo.freeswap;
virtualMemUsed *= memInfo.mem_unit;
return static_cast<uint64_t>(virtualMemUsed);
#elif defined(__FreeBSD__)
kvm_t *kd;
u_int pageSize;
u_int pageCnt, freeCnt;
size_t pageCntLen = sizeof(pageCnt);
size_t freeCntLen = sizeof(freeCnt);
struct kvm_swap kswap;
uint64_t virtualMemUsed;
pageSize = static_cast<u_int>(getpagesize());
sysctlbyname("vm.stats.vm.v_page_count", &pageCnt, &pageCntLen, NULL, 0);
sysctlbyname("vm.stats.vm.v_free_count", &freeCnt, &freeCntLen, NULL, 0);
virtualMemUsed = (pageCnt - freeCnt) * pageSize;
kd = kvm_open(NULL, _PATH_DEVNULL, NULL, O_RDONLY, "kvm_open");
kvm_getswapinfo(kd, &kswap, 1, 0);
kvm_close(kd);
virtualMemUsed += kswap.ksw_used * pageSize;
return virtualMemUsed;
#else
return 0;
#endif
}
uint64_t GetProcessMemoryUsed()
{
#ifdef SPP_WIN
PROCESS_MEMORY_COUNTERS_EX pmc;
GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast<PPROCESS_MEMORY_COUNTERS>(&pmc), sizeof(pmc));
return static_cast<uint64_t>(pmc.PrivateUsage);
#elif defined(__linux__)
auto parseLine =
[](char* line)->int
{
auto i = strlen(line);
while(*line < '0' || *line > '9')
{
line++;
}
line[i-3] = '\0';
i = atoi(line);
return i;
};
auto file = fopen("/proc/self/status", "r");
auto result = -1;
char line[128];
while(fgets(line, 128, file) != nullptr)
{
if(strncmp(line, "VmSize:", 7) == 0)
{
result = parseLine(line);
break;
}
}
fclose(file);
return static_cast<uint64_t>(result) * 1024;
#elif defined(__FreeBSD__)
struct kinfo_proc info;
size_t infoLen = sizeof(info);
int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() };
sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &infoLen, NULL, 0);
return static_cast<uint64_t>(info.ki_rssize * getpagesize());
#else
return 0;
#endif
}
uint64_t GetPhysicalMemory()
{
#ifdef SPP_WIN
MEMORYSTATUSEX memInfo;
memInfo.dwLength = sizeof(MEMORYSTATUSEX);
GlobalMemoryStatusEx(&memInfo);
return static_cast<uint64_t>(memInfo.ullTotalPhys);
#elif defined(__linux__)
struct sysinfo memInfo;
sysinfo(&memInfo);
auto totalPhysMem = memInfo.totalram;
totalPhysMem *= memInfo.mem_unit;
return static_cast<uint64_t>(totalPhysMem);
#elif defined(__FreeBSD__)
u_long physMem;
size_t physMemLen = sizeof(physMem);
int mib[] = { CTL_HW, HW_PHYSMEM };
sysctl(mib, sizeof(mib) / sizeof(*mib), &physMem, &physMemLen, NULL, 0);
return physMem;
#else
return 0;
#endif
}
}
#endif // spp_memory_h_guard

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,664 @@
#if !defined(phmap_bits_h_guard_)
#define phmap_bits_h_guard_
// ---------------------------------------------------------------------------
// Copyright (c) 2019, Gregory Popovitch - greg7mdp@gmail.com
//
// 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
//
// https://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
// limitations under the License.
//
// Includes work from abseil-cpp (https://github.com/abseil/abseil-cpp)
// with modifications.
//
// Copyright 2018 The Abseil 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
//
// https://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
// limitations under the License.
// ---------------------------------------------------------------------------
// The following guarantees declaration of the byte swap functions
#ifdef _MSC_VER
#include <stdlib.h> // NOLINT(build/include)
#elif defined(__APPLE__)
// Mac OS X / Darwin features
#include <libkern/OSByteOrder.h>
#elif defined(__FreeBSD__)
#include <sys/endian.h>
#elif defined(__GLIBC__)
#include <byteswap.h> // IWYU pragma: export
#endif
#include <string.h>
#include <cstdint>
#include "phmap_config.h"
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4514) // unreferenced inline function has been removed
#endif
// -----------------------------------------------------------------------------
// unaligned APIs
// -----------------------------------------------------------------------------
// Portable handling of unaligned loads, stores, and copies.
// On some platforms, like ARM, the copy functions can be more efficient
// then a load and a store.
// -----------------------------------------------------------------------------
#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) ||\
defined(MEMORY_SANITIZER)
#include <stdint.h>
extern "C" {
uint16_t __sanitizer_unaligned_load16(const void *p);
uint32_t __sanitizer_unaligned_load32(const void *p);
uint64_t __sanitizer_unaligned_load64(const void *p);
void __sanitizer_unaligned_store16(void *p, uint16_t v);
void __sanitizer_unaligned_store32(void *p, uint32_t v);
void __sanitizer_unaligned_store64(void *p, uint64_t v);
} // extern "C"
namespace phmap {
namespace bits {
inline uint16_t UnalignedLoad16(const void *p) {
return __sanitizer_unaligned_load16(p);
}
inline uint32_t UnalignedLoad32(const void *p) {
return __sanitizer_unaligned_load32(p);
}
inline uint64_t UnalignedLoad64(const void *p) {
return __sanitizer_unaligned_load64(p);
}
inline void UnalignedStore16(void *p, uint16_t v) {
__sanitizer_unaligned_store16(p, v);
}
inline void UnalignedStore32(void *p, uint32_t v) {
__sanitizer_unaligned_store32(p, v);
}
inline void UnalignedStore64(void *p, uint64_t v) {
__sanitizer_unaligned_store64(p, v);
}
} // namespace bits
} // namespace phmap
#define PHMAP_INTERNAL_UNALIGNED_LOAD16(_p) (phmap::bits::UnalignedLoad16(_p))
#define PHMAP_INTERNAL_UNALIGNED_LOAD32(_p) (phmap::bits::UnalignedLoad32(_p))
#define PHMAP_INTERNAL_UNALIGNED_LOAD64(_p) (phmap::bits::UnalignedLoad64(_p))
#define PHMAP_INTERNAL_UNALIGNED_STORE16(_p, _val) (phmap::bits::UnalignedStore16(_p, _val))
#define PHMAP_INTERNAL_UNALIGNED_STORE32(_p, _val) (phmap::bits::UnalignedStore32(_p, _val))
#define PHMAP_INTERNAL_UNALIGNED_STORE64(_p, _val) (phmap::bits::UnalignedStore64(_p, _val))
#else
namespace phmap {
namespace bits {
inline uint16_t UnalignedLoad16(const void *p) {
uint16_t t;
memcpy(&t, p, sizeof t);
return t;
}
inline uint32_t UnalignedLoad32(const void *p) {
uint32_t t;
memcpy(&t, p, sizeof t);
return t;
}
inline uint64_t UnalignedLoad64(const void *p) {
uint64_t t;
memcpy(&t, p, sizeof t);
return t;
}
inline void UnalignedStore16(void *p, uint16_t v) { memcpy(p, &v, sizeof v); }
inline void UnalignedStore32(void *p, uint32_t v) { memcpy(p, &v, sizeof v); }
inline void UnalignedStore64(void *p, uint64_t v) { memcpy(p, &v, sizeof v); }
} // namespace bits
} // namespace phmap
#define PHMAP_INTERNAL_UNALIGNED_LOAD16(_p) (phmap::bits::UnalignedLoad16(_p))
#define PHMAP_INTERNAL_UNALIGNED_LOAD32(_p) (phmap::bits::UnalignedLoad32(_p))
#define PHMAP_INTERNAL_UNALIGNED_LOAD64(_p) (phmap::bits::UnalignedLoad64(_p))
#define PHMAP_INTERNAL_UNALIGNED_STORE16(_p, _val) (phmap::bits::UnalignedStore16(_p, _val))
#define PHMAP_INTERNAL_UNALIGNED_STORE32(_p, _val) (phmap::bits::UnalignedStore32(_p, _val))
#define PHMAP_INTERNAL_UNALIGNED_STORE64(_p, _val) (phmap::bits::UnalignedStore64(_p, _val))
#endif
// -----------------------------------------------------------------------------
// File: optimization.h
// -----------------------------------------------------------------------------
#if defined(__pnacl__)
#define PHMAP_BLOCK_TAIL_CALL_OPTIMIZATION() if (volatile int x = 0) { (void)x; }
#elif defined(__clang__)
// Clang will not tail call given inline volatile assembly.
#define PHMAP_BLOCK_TAIL_CALL_OPTIMIZATION() __asm__ __volatile__("")
#elif defined(__GNUC__)
// GCC will not tail call given inline volatile assembly.
#define PHMAP_BLOCK_TAIL_CALL_OPTIMIZATION() __asm__ __volatile__("")
#elif defined(_MSC_VER)
#include <intrin.h>
// The __nop() intrinsic blocks the optimisation.
#define PHMAP_BLOCK_TAIL_CALL_OPTIMIZATION() __nop()
#else
#define PHMAP_BLOCK_TAIL_CALL_OPTIMIZATION() if (volatile int x = 0) { (void)x; }
#endif
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif
#ifdef PHMAP_HAVE_INTRINSIC_INT128
__extension__ typedef unsigned __int128 phmap_uint128;
inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high)
{
auto result = static_cast<phmap_uint128>(a) * static_cast<phmap_uint128>(b);
*high = static_cast<uint64_t>(result >> 64);
return static_cast<uint64_t>(result);
}
#define PHMAP_HAS_UMUL128 1
#elif (defined(_MSC_VER))
#if defined(_M_X64)
#pragma intrinsic(_umul128)
inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high)
{
return _umul128(a, b, high);
}
#define PHMAP_HAS_UMUL128 1
#endif
#endif
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
#if defined(__GNUC__)
// Cache line alignment
#if defined(__i386__) || defined(__x86_64__)
#define PHMAP_CACHELINE_SIZE 64
#elif defined(__powerpc64__)
#define PHMAP_CACHELINE_SIZE 128
#elif defined(__aarch64__)
// We would need to read special register ctr_el0 to find out L1 dcache size.
// This value is a good estimate based on a real aarch64 machine.
#define PHMAP_CACHELINE_SIZE 64
#elif defined(__arm__)
// Cache line sizes for ARM: These values are not strictly correct since
// cache line sizes depend on implementations, not architectures. There
// are even implementations with cache line sizes configurable at boot
// time.
#if defined(__ARM_ARCH_5T__)
#define PHMAP_CACHELINE_SIZE 32
#elif defined(__ARM_ARCH_7A__)
#define PHMAP_CACHELINE_SIZE 64
#endif
#endif
#ifndef PHMAP_CACHELINE_SIZE
// A reasonable default guess. Note that overestimates tend to waste more
// space, while underestimates tend to waste more time.
#define PHMAP_CACHELINE_SIZE 64
#endif
#define PHMAP_CACHELINE_ALIGNED __attribute__((aligned(PHMAP_CACHELINE_SIZE)))
#elif defined(_MSC_VER)
#define PHMAP_CACHELINE_SIZE 64
#define PHMAP_CACHELINE_ALIGNED __declspec(align(PHMAP_CACHELINE_SIZE))
#else
#define PHMAP_CACHELINE_SIZE 64
#define PHMAP_CACHELINE_ALIGNED
#endif
#if PHMAP_HAVE_BUILTIN(__builtin_expect) || \
(defined(__GNUC__) && !defined(__clang__))
#define PHMAP_PREDICT_FALSE(x) (__builtin_expect(x, 0))
#define PHMAP_PREDICT_TRUE(x) (__builtin_expect(!!(x), 1))
#else
#define PHMAP_PREDICT_FALSE(x) (x)
#define PHMAP_PREDICT_TRUE(x) (x)
#endif
// -----------------------------------------------------------------------------
// File: bits.h
// -----------------------------------------------------------------------------
#if defined(_MSC_VER)
// We can achieve something similar to attribute((always_inline)) with MSVC by
// using the __forceinline keyword, however this is not perfect. MSVC is
// much less aggressive about inlining, and even with the __forceinline keyword.
#define PHMAP_BASE_INTERNAL_FORCEINLINE __forceinline
#else
// Use default attribute inline.
#define PHMAP_BASE_INTERNAL_FORCEINLINE inline PHMAP_ATTRIBUTE_ALWAYS_INLINE
#endif
namespace phmap {
namespace base_internal {
PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountLeadingZeros64Slow(uint64_t n) {
int zeroes = 60;
if (n >> 32) zeroes -= 32, n >>= 32;
if (n >> 16) zeroes -= 16, n >>= 16;
if (n >> 8) zeroes -= 8, n >>= 8;
if (n >> 4) zeroes -= 4, n >>= 4;
return (uint32_t)("\4\3\2\2\1\1\1\1\0\0\0\0\0\0\0"[n] + zeroes);
}
PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountLeadingZeros64(uint64_t n) {
#if defined(_MSC_VER) && defined(_M_X64)
// MSVC does not have __buitin_clzll. Use _BitScanReverse64.
unsigned long result = 0; // NOLINT(runtime/int)
if (_BitScanReverse64(&result, n)) {
return (uint32_t)(63 - result);
}
return 64;
#elif defined(_MSC_VER) && !defined(__clang__)
// MSVC does not have __buitin_clzll. Compose two calls to _BitScanReverse
unsigned long result = 0; // NOLINT(runtime/int)
if ((n >> 32) && _BitScanReverse(&result, (unsigned long)(n >> 32))) {
return (uint32_t)(31 - result);
}
if (_BitScanReverse(&result, (unsigned long)n)) {
return (uint32_t)(63 - result);
}
return 64;
#elif defined(__GNUC__) || defined(__clang__)
// Use __builtin_clzll, which uses the following instructions:
// x86: bsr
// ARM64: clz
// PPC: cntlzd
static_assert(sizeof(unsigned long long) == sizeof(n), // NOLINT(runtime/int)
"__builtin_clzll does not take 64-bit arg");
// Handle 0 as a special case because __builtin_clzll(0) is undefined.
if (n == 0) {
return 64;
}
return (uint32_t)__builtin_clzll(n);
#else
return CountLeadingZeros64Slow(n);
#endif
}
PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountLeadingZeros32Slow(uint64_t n) {
uint32_t zeroes = 28;
if (n >> 16) zeroes -= 16, n >>= 16;
if (n >> 8) zeroes -= 8, n >>= 8;
if (n >> 4) zeroes -= 4, n >>= 4;
return "\4\3\2\2\1\1\1\1\0\0\0\0\0\0\0"[n] + zeroes;
}
PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountLeadingZeros32(uint32_t n) {
#if defined(_MSC_VER) && !defined(__clang__)
unsigned long result = 0; // NOLINT(runtime/int)
if (_BitScanReverse(&result, n)) {
return (uint32_t)(31 - result);
}
return 32;
#elif defined(__GNUC__) || defined(__clang__)
// Use __builtin_clz, which uses the following instructions:
// x86: bsr
// ARM64: clz
// PPC: cntlzd
static_assert(sizeof(int) == sizeof(n),
"__builtin_clz does not take 32-bit arg");
// Handle 0 as a special case because __builtin_clz(0) is undefined.
if (n == 0) {
return 32;
}
return __builtin_clz(n);
#else
return CountLeadingZeros32Slow(n);
#endif
}
PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountTrailingZerosNonZero64Slow(uint64_t n) {
uint32_t c = 63;
n &= ~n + 1;
if (n & 0x00000000FFFFFFFF) c -= 32;
if (n & 0x0000FFFF0000FFFF) c -= 16;
if (n & 0x00FF00FF00FF00FF) c -= 8;
if (n & 0x0F0F0F0F0F0F0F0F) c -= 4;
if (n & 0x3333333333333333) c -= 2;
if (n & 0x5555555555555555) c -= 1;
return c;
}
PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountTrailingZerosNonZero64(uint64_t n) {
#if defined(_MSC_VER) && !defined(__clang__) && defined(_M_X64)
unsigned long result = 0; // NOLINT(runtime/int)
_BitScanForward64(&result, n);
return (uint32_t)result;
#elif defined(_MSC_VER) && !defined(__clang__)
unsigned long result = 0; // NOLINT(runtime/int)
if (static_cast<uint32_t>(n) == 0) {
_BitScanForward(&result, (unsigned long)(n >> 32));
return result + 32;
}
_BitScanForward(&result, (unsigned long)n);
return result;
#elif defined(__GNUC__) || defined(__clang__)
static_assert(sizeof(unsigned long long) == sizeof(n), // NOLINT(runtime/int)
"__builtin_ctzll does not take 64-bit arg");
return __builtin_ctzll(n);
#else
return CountTrailingZerosNonZero64Slow(n);
#endif
}
PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountTrailingZerosNonZero32Slow(uint32_t n) {
uint32_t c = 31;
n &= ~n + 1;
if (n & 0x0000FFFF) c -= 16;
if (n & 0x00FF00FF) c -= 8;
if (n & 0x0F0F0F0F) c -= 4;
if (n & 0x33333333) c -= 2;
if (n & 0x55555555) c -= 1;
return c;
}
PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountTrailingZerosNonZero32(uint32_t n) {
#if defined(_MSC_VER) && !defined(__clang__)
unsigned long result = 0; // NOLINT(runtime/int)
_BitScanForward(&result, n);
return (uint32_t)result;
#elif defined(__GNUC__) || defined(__clang__)
static_assert(sizeof(int) == sizeof(n),
"__builtin_ctz does not take 32-bit arg");
return __builtin_ctz(n);
#else
return CountTrailingZerosNonZero32Slow(n);
#endif
}
#undef PHMAP_BASE_INTERNAL_FORCEINLINE
} // namespace base_internal
} // namespace phmap
// -----------------------------------------------------------------------------
// File: endian.h
// -----------------------------------------------------------------------------
namespace phmap {
// Use compiler byte-swapping intrinsics if they are available. 32-bit
// and 64-bit versions are available in Clang and GCC as of GCC 4.3.0.
// The 16-bit version is available in Clang and GCC only as of GCC 4.8.0.
// For simplicity, we enable them all only for GCC 4.8.0 or later.
#if defined(__clang__) || \
(defined(__GNUC__) && \
((__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || __GNUC__ >= 5))
inline uint64_t gbswap_64(uint64_t host_int) {
return __builtin_bswap64(host_int);
}
inline uint32_t gbswap_32(uint32_t host_int) {
return __builtin_bswap32(host_int);
}
inline uint16_t gbswap_16(uint16_t host_int) {
return __builtin_bswap16(host_int);
}
#elif defined(_MSC_VER)
inline uint64_t gbswap_64(uint64_t host_int) {
return _byteswap_uint64(host_int);
}
inline uint32_t gbswap_32(uint32_t host_int) {
return _byteswap_ulong(host_int);
}
inline uint16_t gbswap_16(uint16_t host_int) {
return _byteswap_ushort(host_int);
}
#elif defined(__APPLE__)
inline uint64_t gbswap_64(uint64_t host_int) { return OSSwapInt16(host_int); }
inline uint32_t gbswap_32(uint32_t host_int) { return OSSwapInt32(host_int); }
inline uint16_t gbswap_16(uint16_t host_int) { return OSSwapInt64(host_int); }
#else
inline uint64_t gbswap_64(uint64_t host_int) {
#if defined(__GNUC__) && defined(__x86_64__) && !defined(__APPLE__)
// Adapted from /usr/include/byteswap.h. Not available on Mac.
if (__builtin_constant_p(host_int)) {
return __bswap_constant_64(host_int);
} else {
uint64_t result;
__asm__("bswap %0" : "=r"(result) : "0"(host_int));
return result;
}
#elif defined(__GLIBC__)
return bswap_64(host_int);
#else
return (((host_int & uint64_t{0xFF}) << 56) |
((host_int & uint64_t{0xFF00}) << 40) |
((host_int & uint64_t{0xFF0000}) << 24) |
((host_int & uint64_t{0xFF000000}) << 8) |
((host_int & uint64_t{0xFF00000000}) >> 8) |
((host_int & uint64_t{0xFF0000000000}) >> 24) |
((host_int & uint64_t{0xFF000000000000}) >> 40) |
((host_int & uint64_t{0xFF00000000000000}) >> 56));
#endif // bswap_64
}
inline uint32_t gbswap_32(uint32_t host_int) {
#if defined(__GLIBC__)
return bswap_32(host_int);
#else
return (((host_int & uint32_t{0xFF}) << 24) |
((host_int & uint32_t{0xFF00}) << 8) |
((host_int & uint32_t{0xFF0000}) >> 8) |
((host_int & uint32_t{0xFF000000}) >> 24));
#endif
}
inline uint16_t gbswap_16(uint16_t host_int) {
#if defined(__GLIBC__)
return bswap_16(host_int);
#else
return (((host_int & uint16_t{0xFF}) << 8) |
((host_int & uint16_t{0xFF00}) >> 8));
#endif
}
#endif // intrinics available
#ifdef PHMAP_IS_LITTLE_ENDIAN
// Definitions for ntohl etc. that don't require us to include
// netinet/in.h. We wrap gbswap_32 and gbswap_16 in functions rather
// than just #defining them because in debug mode, gcc doesn't
// correctly handle the (rather involved) definitions of bswap_32.
// gcc guarantees that inline functions are as fast as macros, so
// this isn't a performance hit.
inline uint16_t ghtons(uint16_t x) { return gbswap_16(x); }
inline uint32_t ghtonl(uint32_t x) { return gbswap_32(x); }
inline uint64_t ghtonll(uint64_t x) { return gbswap_64(x); }
#elif defined PHMAP_IS_BIG_ENDIAN
// These definitions are simpler on big-endian machines
// These are functions instead of macros to avoid self-assignment warnings
// on calls such as "i = ghtnol(i);". This also provides type checking.
inline uint16_t ghtons(uint16_t x) { return x; }
inline uint32_t ghtonl(uint32_t x) { return x; }
inline uint64_t ghtonll(uint64_t x) { return x; }
#else
#error \
"Unsupported byte order: Either PHMAP_IS_BIG_ENDIAN or " \
"PHMAP_IS_LITTLE_ENDIAN must be defined"
#endif // byte order
inline uint16_t gntohs(uint16_t x) { return ghtons(x); }
inline uint32_t gntohl(uint32_t x) { return ghtonl(x); }
inline uint64_t gntohll(uint64_t x) { return ghtonll(x); }
// Utilities to convert numbers between the current hosts's native byte
// order and little-endian byte order
//
// Load/Store methods are alignment safe
namespace little_endian {
// Conversion functions.
#ifdef PHMAP_IS_LITTLE_ENDIAN
inline uint16_t FromHost16(uint16_t x) { return x; }
inline uint16_t ToHost16(uint16_t x) { return x; }
inline uint32_t FromHost32(uint32_t x) { return x; }
inline uint32_t ToHost32(uint32_t x) { return x; }
inline uint64_t FromHost64(uint64_t x) { return x; }
inline uint64_t ToHost64(uint64_t x) { return x; }
inline constexpr bool IsLittleEndian() { return true; }
#elif defined PHMAP_IS_BIG_ENDIAN
inline uint16_t FromHost16(uint16_t x) { return gbswap_16(x); }
inline uint16_t ToHost16(uint16_t x) { return gbswap_16(x); }
inline uint32_t FromHost32(uint32_t x) { return gbswap_32(x); }
inline uint32_t ToHost32(uint32_t x) { return gbswap_32(x); }
inline uint64_t FromHost64(uint64_t x) { return gbswap_64(x); }
inline uint64_t ToHost64(uint64_t x) { return gbswap_64(x); }
inline constexpr bool IsLittleEndian() { return false; }
#endif /* ENDIAN */
// Functions to do unaligned loads and stores in little-endian order.
// ------------------------------------------------------------------
inline uint16_t Load16(const void *p) {
return ToHost16(PHMAP_INTERNAL_UNALIGNED_LOAD16(p));
}
inline void Store16(void *p, uint16_t v) {
PHMAP_INTERNAL_UNALIGNED_STORE16(p, FromHost16(v));
}
inline uint32_t Load32(const void *p) {
return ToHost32(PHMAP_INTERNAL_UNALIGNED_LOAD32(p));
}
inline void Store32(void *p, uint32_t v) {
PHMAP_INTERNAL_UNALIGNED_STORE32(p, FromHost32(v));
}
inline uint64_t Load64(const void *p) {
return ToHost64(PHMAP_INTERNAL_UNALIGNED_LOAD64(p));
}
inline void Store64(void *p, uint64_t v) {
PHMAP_INTERNAL_UNALIGNED_STORE64(p, FromHost64(v));
}
} // namespace little_endian
// Utilities to convert numbers between the current hosts's native byte
// order and big-endian byte order (same as network byte order)
//
// Load/Store methods are alignment safe
namespace big_endian {
#ifdef PHMAP_IS_LITTLE_ENDIAN
inline uint16_t FromHost16(uint16_t x) { return gbswap_16(x); }
inline uint16_t ToHost16(uint16_t x) { return gbswap_16(x); }
inline uint32_t FromHost32(uint32_t x) { return gbswap_32(x); }
inline uint32_t ToHost32(uint32_t x) { return gbswap_32(x); }
inline uint64_t FromHost64(uint64_t x) { return gbswap_64(x); }
inline uint64_t ToHost64(uint64_t x) { return gbswap_64(x); }
inline constexpr bool IsLittleEndian() { return true; }
#elif defined PHMAP_IS_BIG_ENDIAN
inline uint16_t FromHost16(uint16_t x) { return x; }
inline uint16_t ToHost16(uint16_t x) { return x; }
inline uint32_t FromHost32(uint32_t x) { return x; }
inline uint32_t ToHost32(uint32_t x) { return x; }
inline uint64_t FromHost64(uint64_t x) { return x; }
inline uint64_t ToHost64(uint64_t x) { return x; }
inline constexpr bool IsLittleEndian() { return false; }
#endif /* ENDIAN */
// Functions to do unaligned loads and stores in big-endian order.
inline uint16_t Load16(const void *p) {
return ToHost16(PHMAP_INTERNAL_UNALIGNED_LOAD16(p));
}
inline void Store16(void *p, uint16_t v) {
PHMAP_INTERNAL_UNALIGNED_STORE16(p, FromHost16(v));
}
inline uint32_t Load32(const void *p) {
return ToHost32(PHMAP_INTERNAL_UNALIGNED_LOAD32(p));
}
inline void Store32(void *p, uint32_t v) {
PHMAP_INTERNAL_UNALIGNED_STORE32(p, FromHost32(v));
}
inline uint64_t Load64(const void *p) {
return ToHost64(PHMAP_INTERNAL_UNALIGNED_LOAD64(p));
}
inline void Store64(void *p, uint64_t v) {
PHMAP_INTERNAL_UNALIGNED_STORE64(p, FromHost64(v));
}
} // namespace big_endian
} // namespace phmap
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif // phmap_bits_h_guard_

View file

@ -0,0 +1,767 @@
#if !defined(phmap_config_h_guard_)
#define phmap_config_h_guard_
// ---------------------------------------------------------------------------
// Copyright (c) 2019, Gregory Popovitch - greg7mdp@gmail.com
//
// 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
//
// https://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
// limitations under the License.
//
// Includes work from abseil-cpp (https://github.com/abseil/abseil-cpp)
// with modifications.
//
// Copyright 2018 The Abseil 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
//
// https://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
// limitations under the License.
// ---------------------------------------------------------------------------
#define PHMAP_VERSION_MAJOR 1
#define PHMAP_VERSION_MINOR 3
#define PHMAP_VERSION_PATCH 12
// Included for the __GLIBC__ macro (or similar macros on other systems).
#include <limits.h>
#ifdef __cplusplus
// Included for __GLIBCXX__, _LIBCPP_VERSION
#include <cstddef>
#endif // __cplusplus
#if defined(__APPLE__)
// Included for TARGET_OS_IPHONE, __IPHONE_OS_VERSION_MIN_REQUIRED,
// __IPHONE_8_0.
#include <Availability.h>
#include <TargetConditionals.h>
#endif
#define PHMAP_XSTR(x) PHMAP_STR(x)
#define PHMAP_STR(x) #x
#define PHMAP_VAR_NAME_VALUE(var) #var "=" PHMAP_STR(var)
// -----------------------------------------------------------------------------
// Some sanity checks
// -----------------------------------------------------------------------------
//#if defined(__CYGWIN__)
// #error "Cygwin is not supported."
//#endif
#if defined(_MSC_FULL_VER) && _MSC_FULL_VER < 190023918 && !defined(__clang__)
#error "phmap requires Visual Studio 2015 Update 2 or higher."
#endif
// We support gcc 4.7 and later.
#if defined(__GNUC__) && !defined(__clang__)
#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7)
#error "phmap requires gcc 4.7 or higher."
#endif
#endif
// We support Apple Xcode clang 4.2.1 (version 421.11.65) and later.
// This corresponds to Apple Xcode version 4.5.
#if defined(__apple_build_version__) && __apple_build_version__ < 4211165
#error "phmap requires __apple_build_version__ of 4211165 or higher."
#endif
// Enforce C++11 as the minimum.
#if defined(__cplusplus) && !defined(_MSC_VER)
#if __cplusplus < 201103L
#error "C++ versions less than C++11 are not supported."
#endif
#endif
// We have chosen glibc 2.12 as the minimum
#if defined(__GLIBC__) && defined(__GLIBC_PREREQ)
#if !__GLIBC_PREREQ(2, 12)
#error "Minimum required version of glibc is 2.12."
#endif
#endif
#if defined(_STLPORT_VERSION)
#error "STLPort is not supported."
#endif
#if CHAR_BIT != 8
#warning "phmap assumes CHAR_BIT == 8."
#endif
// phmap currently assumes that an int is 4 bytes.
#if INT_MAX < 2147483647
#error "phmap assumes that int is at least 4 bytes. "
#endif
// -----------------------------------------------------------------------------
// Compiler Feature Checks
// -----------------------------------------------------------------------------
#ifdef __has_builtin
#define PHMAP_HAVE_BUILTIN(x) __has_builtin(x)
#else
#define PHMAP_HAVE_BUILTIN(x) 0
#endif
#if (!defined(__GNUC__) || defined(__clang__) || __GNUC__ >= 5) && \
((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
#define PHMAP_HAVE_CC17 1
#else
#define PHMAP_HAVE_CC17 0
#endif
#define PHMAP_BRANCHLESS 1
#ifdef __has_feature
#define PHMAP_HAVE_FEATURE(f) __has_feature(f)
#else
#define PHMAP_HAVE_FEATURE(f) 0
#endif
// Portable check for GCC minimum version:
// https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
#if defined(__GNUC__) && defined(__GNUC_MINOR__)
#define PHMAP_INTERNAL_HAVE_MIN_GNUC_VERSION(x, y) (__GNUC__ > (x) || __GNUC__ == (x) && __GNUC_MINOR__ >= (y))
#else
#define PHMAP_INTERNAL_HAVE_MIN_GNUC_VERSION(x, y) 0
#endif
#if defined(__clang__) && defined(__clang_major__) && defined(__clang_minor__)
#define PHMAP_INTERNAL_HAVE_MIN_CLANG_VERSION(x, y) (__clang_major__ > (x) || __clang_major__ == (x) && __clang_minor__ >= (y))
#else
#define PHMAP_INTERNAL_HAVE_MIN_CLANG_VERSION(x, y) 0
#endif
// -------------------------------------------------------------------
// Checks whether C++11's `thread_local` storage duration specifier is
// supported.
// -------------------------------------------------------------------
#ifdef PHMAP_HAVE_THREAD_LOCAL
#error PHMAP_HAVE_THREAD_LOCAL cannot be directly set
#elif defined(__APPLE__) && defined(__clang__)
#if __has_feature(cxx_thread_local) && \
!(TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
#define PHMAP_HAVE_THREAD_LOCAL 1
#endif
#else // !defined(__APPLE__)
#define PHMAP_HAVE_THREAD_LOCAL 1
#endif
#if defined(__ANDROID__) && defined(__clang__)
#if __has_include(<android/ndk-version.h>)
#include <android/ndk-version.h>
#endif // __has_include(<android/ndk-version.h>)
#if defined(__ANDROID__) && defined(__clang__) && defined(__NDK_MAJOR__) && \
defined(__NDK_MINOR__) && \
((__NDK_MAJOR__ < 12) || ((__NDK_MAJOR__ == 12) && (__NDK_MINOR__ < 1)))
#undef PHMAP_HAVE_TLS
#undef PHMAP_HAVE_THREAD_LOCAL
#endif
#endif
// ------------------------------------------------------------
// Checks whether the __int128 compiler extension for a 128-bit
// integral type is supported.
// ------------------------------------------------------------
#ifdef PHMAP_HAVE_INTRINSIC_INT128
#error PHMAP_HAVE_INTRINSIC_INT128 cannot be directly set
#elif defined(__SIZEOF_INT128__)
#if (defined(__clang__) && !defined(_WIN32) && !defined(__aarch64__)) || \
(defined(__CUDACC__) && __CUDACC_VER_MAJOR__ >= 9) || \
(defined(__GNUC__) && !defined(__clang__) && !defined(__CUDACC__))
#define PHMAP_HAVE_INTRINSIC_INT128 1
#elif defined(__CUDACC__)
#if __CUDACC_VER__ >= 70000
#define PHMAP_HAVE_INTRINSIC_INT128 1
#endif // __CUDACC_VER__ >= 70000
#endif // defined(__CUDACC__)
#endif
// ------------------------------------------------------------------
// Checks whether the compiler both supports and enables exceptions.
// ------------------------------------------------------------------
#ifdef PHMAP_HAVE_EXCEPTIONS
#error PHMAP_HAVE_EXCEPTIONS cannot be directly set.
#elif defined(__clang__)
#if defined(__EXCEPTIONS) && __has_feature(cxx_exceptions)
#define PHMAP_HAVE_EXCEPTIONS 1
#endif // defined(__EXCEPTIONS) && __has_feature(cxx_exceptions)
#elif !(defined(__GNUC__) && (__GNUC__ < 5) && !defined(__EXCEPTIONS)) && \
!(defined(__GNUC__) && (__GNUC__ >= 5) && !defined(__cpp_exceptions)) && \
!(defined(_MSC_VER) && !defined(_CPPUNWIND))
#define PHMAP_HAVE_EXCEPTIONS 1
#endif
// -----------------------------------------------------------------------
// Checks whether the platform has an mmap(2) implementation as defined in
// POSIX.1-2001.
// -----------------------------------------------------------------------
#ifdef PHMAP_HAVE_MMAP
#error PHMAP_HAVE_MMAP cannot be directly set
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \
defined(__ros__) || defined(__native_client__) || defined(__asmjs__) || \
defined(__wasm__) || defined(__Fuchsia__) || defined(__sun) || \
defined(__ASYLO__)
#define PHMAP_HAVE_MMAP 1
#endif
// -----------------------------------------------------------------------
// Checks the endianness of the platform.
// -----------------------------------------------------------------------
#if defined(PHMAP_IS_BIG_ENDIAN)
#error "PHMAP_IS_BIG_ENDIAN cannot be directly set."
#endif
#if defined(PHMAP_IS_LITTLE_ENDIAN)
#error "PHMAP_IS_LITTLE_ENDIAN cannot be directly set."
#endif
#if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \
__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
#define PHMAP_IS_LITTLE_ENDIAN 1
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \
__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define PHMAP_IS_BIG_ENDIAN 1
#elif defined(_WIN32)
#define PHMAP_IS_LITTLE_ENDIAN 1
#else
#error "phmap endian detection needs to be set up for your compiler"
#endif
#if defined(__APPLE__) && defined(_LIBCPP_VERSION) && \
defined(__MAC_OS_X_VERSION_MIN_REQUIRED__) && \
__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 101400
#define PHMAP_INTERNAL_MACOS_CXX17_TYPES_UNAVAILABLE 1
#else
#define PHMAP_INTERNAL_MACOS_CXX17_TYPES_UNAVAILABLE 0
#endif
// ---------------------------------------------------------------------------
// Checks whether C++17 std::any is available by checking whether <any> exists.
// ---------------------------------------------------------------------------
#ifdef PHMAP_HAVE_STD_ANY
#error "PHMAP_HAVE_STD_ANY cannot be directly set."
#endif
#ifdef __has_include
#if __has_include(<any>) && __cplusplus >= 201703L && \
!PHMAP_INTERNAL_MACOS_CXX17_TYPES_UNAVAILABLE
#define PHMAP_HAVE_STD_ANY 1
#endif
#endif
#ifdef PHMAP_HAVE_STD_OPTIONAL
#error "PHMAP_HAVE_STD_OPTIONAL cannot be directly set."
#endif
#ifdef __has_include
#if __has_include(<optional>) && __cplusplus >= 201703L && \
!PHMAP_INTERNAL_MACOS_CXX17_TYPES_UNAVAILABLE
#define PHMAP_HAVE_STD_OPTIONAL 1
#endif
#endif
#ifdef PHMAP_HAVE_STD_VARIANT
#error "PHMAP_HAVE_STD_VARIANT cannot be directly set."
#endif
#ifdef __has_include
#if __has_include(<variant>) && __cplusplus >= 201703L && \
!PHMAP_INTERNAL_MACOS_CXX17_TYPES_UNAVAILABLE
#define PHMAP_HAVE_STD_VARIANT 1
#endif
#endif
#ifdef PHMAP_HAVE_STD_STRING_VIEW
#error "PHMAP_HAVE_STD_STRING_VIEW cannot be directly set."
#endif
#ifdef __has_include
#if __has_include(<string_view>) && __cplusplus >= 201703L && \
(!defined(_MSC_VER) || _MSC_VER >= 1920) // vs2019
#define PHMAP_HAVE_STD_STRING_VIEW 1
#endif
#endif
// #pragma message(PHMAP_VAR_NAME_VALUE(_MSVC_LANG))
#if defined(_MSC_VER) && _MSC_VER >= 1910 && PHMAP_HAVE_CC17
// #define PHMAP_HAVE_STD_ANY 1
#define PHMAP_HAVE_STD_OPTIONAL 1
#define PHMAP_HAVE_STD_VARIANT 1
#if !defined(PHMAP_HAVE_STD_STRING_VIEW) && _MSC_VER >= 1920
#define PHMAP_HAVE_STD_STRING_VIEW 1
#endif
#endif
#if PHMAP_HAVE_CC17
#ifdef __has_include
#if __has_include(<shared_mutex>)
#define PHMAP_HAVE_SHARED_MUTEX 1
#endif
#endif
#endif
#ifndef PHMAP_HAVE_STD_STRING_VIEW
#define PHMAP_HAVE_STD_STRING_VIEW 0
#endif
// In debug mode, MSVC 2017's std::variant throws a EXCEPTION_ACCESS_VIOLATION
// SEH exception from emplace for variant<SomeStruct> when constructing the
// struct can throw. This defeats some of variant_test and
// variant_exception_safety_test.
#if defined(_MSC_VER) && _MSC_VER >= 1700 && defined(_DEBUG)
#define PHMAP_INTERNAL_MSVC_2017_DBG_MODE
#endif
// ---------------------------------------------------------------------------
// Checks whether wchar_t is treated as a native type
// (MSVC: /Zc:wchar_t- treats wchar_t as unsigned short)
// ---------------------------------------------------------------------------
#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED)
#define PHMAP_HAS_NATIVE_WCHAR_T
#endif
// -----------------------------------------------------------------------------
// Sanitizer Attributes
// -----------------------------------------------------------------------------
//
// Sanitizer-related attributes are not "defined" in this file (and indeed
// are not defined as such in any file). To utilize the following
// sanitizer-related attributes within your builds, define the following macros
// within your build using a `-D` flag, along with the given value for
// `-fsanitize`:
//
// * `ADDRESS_SANITIZER` + `-fsanitize=address` (Clang, GCC 4.8)
// * `MEMORY_SANITIZER` + `-fsanitize=memory` (Clang-only)
// * `THREAD_SANITIZER + `-fsanitize=thread` (Clang, GCC 4.8+)
// * `UNDEFINED_BEHAVIOR_SANITIZER` + `-fsanitize=undefined` (Clang, GCC 4.9+)
// * `CONTROL_FLOW_INTEGRITY` + -fsanitize=cfi (Clang-only)
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// A function-like feature checking macro that is a wrapper around
// `__has_attribute`, which is defined by GCC 5+ and Clang and evaluates to a
// nonzero constant integer if the attribute is supported or 0 if not.
//
// It evaluates to zero if `__has_attribute` is not defined by the compiler.
// -----------------------------------------------------------------------------
#ifdef __has_attribute
#define PHMAP_HAVE_ATTRIBUTE(x) __has_attribute(x)
#else
#define PHMAP_HAVE_ATTRIBUTE(x) 0
#endif
// -----------------------------------------------------------------------------
// A function-like feature checking macro that accepts C++11 style attributes.
// It's a wrapper around `__has_cpp_attribute`, defined by ISO C++ SD-6
// (https://en.cppreference.com/w/cpp/experimental/feature_test). If we don't
// find `__has_cpp_attribute`, will evaluate to 0.
// -----------------------------------------------------------------------------
#if defined(__cplusplus) && defined(__has_cpp_attribute)
#define PHMAP_HAVE_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
#else
#define PHMAP_HAVE_CPP_ATTRIBUTE(x) 0
#endif
// -----------------------------------------------------------------------------
// Function Attributes
// -----------------------------------------------------------------------------
#if PHMAP_HAVE_ATTRIBUTE(format) || (defined(__GNUC__) && !defined(__clang__))
#define PHMAP_PRINTF_ATTRIBUTE(string_index, first_to_check) \
__attribute__((__format__(__printf__, string_index, first_to_check)))
#define PHMAP_SCANF_ATTRIBUTE(string_index, first_to_check) \
__attribute__((__format__(__scanf__, string_index, first_to_check)))
#else
#define PHMAP_PRINTF_ATTRIBUTE(string_index, first_to_check)
#define PHMAP_SCANF_ATTRIBUTE(string_index, first_to_check)
#endif
#if PHMAP_HAVE_ATTRIBUTE(always_inline) || \
(defined(__GNUC__) && !defined(__clang__))
#define PHMAP_ATTRIBUTE_ALWAYS_INLINE __attribute__((always_inline))
#define PHMAP_HAVE_ATTRIBUTE_ALWAYS_INLINE 1
#else
#define PHMAP_ATTRIBUTE_ALWAYS_INLINE
#endif
#if !defined(__INTEL_COMPILER) && (PHMAP_HAVE_ATTRIBUTE(noinline) || (defined(__GNUC__) && !defined(__clang__)))
#define PHMAP_ATTRIBUTE_NOINLINE __attribute__((noinline))
#define PHMAP_HAVE_ATTRIBUTE_NOINLINE 1
#else
#define PHMAP_ATTRIBUTE_NOINLINE
#endif
#if PHMAP_HAVE_ATTRIBUTE(disable_tail_calls)
#define PHMAP_HAVE_ATTRIBUTE_NO_TAIL_CALL 1
#define PHMAP_ATTRIBUTE_NO_TAIL_CALL __attribute__((disable_tail_calls))
#elif defined(__GNUC__) && !defined(__clang__)
#define PHMAP_HAVE_ATTRIBUTE_NO_TAIL_CALL 1
#define PHMAP_ATTRIBUTE_NO_TAIL_CALL \
__attribute__((optimize("no-optimize-sibling-calls")))
#else
#define PHMAP_ATTRIBUTE_NO_TAIL_CALL
#define PHMAP_HAVE_ATTRIBUTE_NO_TAIL_CALL 0
#endif
#if (PHMAP_HAVE_ATTRIBUTE(weak) || \
(defined(__GNUC__) && !defined(__clang__))) && \
!(defined(__llvm__) && defined(_WIN32))
#undef PHMAP_ATTRIBUTE_WEAK
#define PHMAP_ATTRIBUTE_WEAK __attribute__((weak))
#define PHMAP_HAVE_ATTRIBUTE_WEAK 1
#else
#define PHMAP_ATTRIBUTE_WEAK
#define PHMAP_HAVE_ATTRIBUTE_WEAK 0
#endif
#if PHMAP_HAVE_ATTRIBUTE(nonnull) || (defined(__GNUC__) && !defined(__clang__))
#define PHMAP_ATTRIBUTE_NONNULL(arg_index) __attribute__((nonnull(arg_index)))
#else
#define PHMAP_ATTRIBUTE_NONNULL(...)
#endif
#if PHMAP_HAVE_ATTRIBUTE(noreturn) || (defined(__GNUC__) && !defined(__clang__))
#define PHMAP_ATTRIBUTE_NORETURN __attribute__((noreturn))
#elif defined(_MSC_VER)
#define PHMAP_ATTRIBUTE_NORETURN __declspec(noreturn)
#else
#define PHMAP_ATTRIBUTE_NORETURN
#endif
#if defined(__GNUC__) && defined(ADDRESS_SANITIZER)
#define PHMAP_ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
#else
#define PHMAP_ATTRIBUTE_NO_SANITIZE_ADDRESS
#endif
#if defined(__GNUC__) && defined(MEMORY_SANITIZER)
#define PHMAP_ATTRIBUTE_NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory))
#else
#define PHMAP_ATTRIBUTE_NO_SANITIZE_MEMORY
#endif
#if defined(__GNUC__) && defined(THREAD_SANITIZER)
#define PHMAP_ATTRIBUTE_NO_SANITIZE_THREAD __attribute__((no_sanitize_thread))
#else
#define PHMAP_ATTRIBUTE_NO_SANITIZE_THREAD
#endif
#if defined(__GNUC__) && \
(defined(UNDEFINED_BEHAVIOR_SANITIZER) || defined(ADDRESS_SANITIZER))
#define PHMAP_ATTRIBUTE_NO_SANITIZE_UNDEFINED \
__attribute__((no_sanitize("undefined")))
#else
#define PHMAP_ATTRIBUTE_NO_SANITIZE_UNDEFINED
#endif
#if defined(__GNUC__) && defined(CONTROL_FLOW_INTEGRITY)
#define PHMAP_ATTRIBUTE_NO_SANITIZE_CFI __attribute__((no_sanitize("cfi")))
#else
#define PHMAP_ATTRIBUTE_NO_SANITIZE_CFI
#endif
#if defined(__GNUC__) && defined(SAFESTACK_SANITIZER)
#define PHMAP_ATTRIBUTE_NO_SANITIZE_SAFESTACK \
__attribute__((no_sanitize("safe-stack")))
#else
#define PHMAP_ATTRIBUTE_NO_SANITIZE_SAFESTACK
#endif
#if PHMAP_HAVE_ATTRIBUTE(returns_nonnull) || \
(defined(__GNUC__) && \
(__GNUC__ > 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) && \
!defined(__clang__))
#define PHMAP_ATTRIBUTE_RETURNS_NONNULL __attribute__((returns_nonnull))
#else
#define PHMAP_ATTRIBUTE_RETURNS_NONNULL
#endif
#ifdef PHMAP_HAVE_ATTRIBUTE_SECTION
#error PHMAP_HAVE_ATTRIBUTE_SECTION cannot be directly set
#elif (PHMAP_HAVE_ATTRIBUTE(section) || \
(defined(__GNUC__) && !defined(__clang__))) && \
!defined(__APPLE__) && PHMAP_HAVE_ATTRIBUTE_WEAK
#define PHMAP_HAVE_ATTRIBUTE_SECTION 1
#ifndef PHMAP_ATTRIBUTE_SECTION
#define PHMAP_ATTRIBUTE_SECTION(name) \
__attribute__((section(#name))) __attribute__((noinline))
#endif
#ifndef PHMAP_ATTRIBUTE_SECTION_VARIABLE
#define PHMAP_ATTRIBUTE_SECTION_VARIABLE(name) __attribute__((section(#name)))
#endif
#ifndef PHMAP_DECLARE_ATTRIBUTE_SECTION_VARS
#define PHMAP_DECLARE_ATTRIBUTE_SECTION_VARS(name) \
extern char __start_##name[] PHMAP_ATTRIBUTE_WEAK; \
extern char __stop_##name[] PHMAP_ATTRIBUTE_WEAK
#endif
#ifndef PHMAP_DEFINE_ATTRIBUTE_SECTION_VARS
#define PHMAP_INIT_ATTRIBUTE_SECTION_VARS(name)
#define PHMAP_DEFINE_ATTRIBUTE_SECTION_VARS(name)
#endif
#define PHMAP_ATTRIBUTE_SECTION_START(name) \
(reinterpret_cast<void *>(__start_##name))
#define PHMAP_ATTRIBUTE_SECTION_STOP(name) \
(reinterpret_cast<void *>(__stop_##name))
#else // !PHMAP_HAVE_ATTRIBUTE_SECTION
#define PHMAP_HAVE_ATTRIBUTE_SECTION 0
#define PHMAP_ATTRIBUTE_SECTION(name)
#define PHMAP_ATTRIBUTE_SECTION_VARIABLE(name)
#define PHMAP_INIT_ATTRIBUTE_SECTION_VARS(name)
#define PHMAP_DEFINE_ATTRIBUTE_SECTION_VARS(name)
#define PHMAP_DECLARE_ATTRIBUTE_SECTION_VARS(name)
#define PHMAP_ATTRIBUTE_SECTION_START(name) (reinterpret_cast<void *>(0))
#define PHMAP_ATTRIBUTE_SECTION_STOP(name) (reinterpret_cast<void *>(0))
#endif // PHMAP_ATTRIBUTE_SECTION
#if PHMAP_HAVE_ATTRIBUTE(force_align_arg_pointer) || \
(defined(__GNUC__) && !defined(__clang__))
#if defined(__i386__)
#define PHMAP_ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC \
__attribute__((force_align_arg_pointer))
#define PHMAP_REQUIRE_STACK_ALIGN_TRAMPOLINE (0)
#elif defined(__x86_64__)
#define PHMAP_REQUIRE_STACK_ALIGN_TRAMPOLINE (1)
#define PHMAP_ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC
#else // !__i386__ && !__x86_64
#define PHMAP_REQUIRE_STACK_ALIGN_TRAMPOLINE (0)
#define PHMAP_ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC
#endif // __i386__
#else
#define PHMAP_ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC
#define PHMAP_REQUIRE_STACK_ALIGN_TRAMPOLINE (0)
#endif
#if PHMAP_HAVE_ATTRIBUTE(nodiscard)
#define PHMAP_MUST_USE_RESULT [[nodiscard]]
#elif defined(__clang__) && PHMAP_HAVE_ATTRIBUTE(warn_unused_result)
#define PHMAP_MUST_USE_RESULT __attribute__((warn_unused_result))
#else
#define PHMAP_MUST_USE_RESULT
#endif
#if PHMAP_HAVE_ATTRIBUTE(hot) || (defined(__GNUC__) && !defined(__clang__))
#define PHMAP_ATTRIBUTE_HOT __attribute__((hot))
#else
#define PHMAP_ATTRIBUTE_HOT
#endif
#if PHMAP_HAVE_ATTRIBUTE(cold) || (defined(__GNUC__) && !defined(__clang__))
#define PHMAP_ATTRIBUTE_COLD __attribute__((cold))
#else
#define PHMAP_ATTRIBUTE_COLD
#endif
#if defined(__clang__)
#if PHMAP_HAVE_CPP_ATTRIBUTE(clang::reinitializes)
#define PHMAP_ATTRIBUTE_REINITIALIZES [[clang::reinitializes]]
#else
#define PHMAP_ATTRIBUTE_REINITIALIZES
#endif
#else
#define PHMAP_ATTRIBUTE_REINITIALIZES
#endif
#if PHMAP_HAVE_ATTRIBUTE(unused) || (defined(__GNUC__) && !defined(__clang__))
#undef PHMAP_ATTRIBUTE_UNUSED
#define PHMAP_ATTRIBUTE_UNUSED __attribute__((__unused__))
#else
#define PHMAP_ATTRIBUTE_UNUSED
#endif
#if PHMAP_HAVE_ATTRIBUTE(tls_model) || (defined(__GNUC__) && !defined(__clang__))
#define PHMAP_ATTRIBUTE_INITIAL_EXEC __attribute__((tls_model("initial-exec")))
#else
#define PHMAP_ATTRIBUTE_INITIAL_EXEC
#endif
#if PHMAP_HAVE_ATTRIBUTE(packed) || (defined(__GNUC__) && !defined(__clang__))
#define PHMAP_ATTRIBUTE_PACKED __attribute__((__packed__))
#else
#define PHMAP_ATTRIBUTE_PACKED
#endif
#if PHMAP_HAVE_ATTRIBUTE(aligned) || (defined(__GNUC__) && !defined(__clang__))
#define PHMAP_ATTRIBUTE_FUNC_ALIGN(bytes) __attribute__((aligned(bytes)))
#else
#define PHMAP_ATTRIBUTE_FUNC_ALIGN(bytes)
#endif
// ----------------------------------------------------------------------
// Figure out SSE support
// ----------------------------------------------------------------------
#ifndef PHMAP_HAVE_SSE2
#if defined(__SSE2__) || \
(defined(_MSC_VER) && \
(defined(_M_X64) || (defined(_M_IX86) && _M_IX86_FP >= 2)))
#define PHMAP_HAVE_SSE2 1
#else
#define PHMAP_HAVE_SSE2 0
#endif
#endif
#ifndef PHMAP_HAVE_SSSE3
#if defined(__SSSE3__) || defined(__AVX2__)
#define PHMAP_HAVE_SSSE3 1
#else
#define PHMAP_HAVE_SSSE3 0
#endif
#endif
#if PHMAP_HAVE_SSSE3 && !PHMAP_HAVE_SSE2
#error "Bad configuration!"
#endif
#if PHMAP_HAVE_SSE2
#include <emmintrin.h>
#endif
#if PHMAP_HAVE_SSSE3
#include <tmmintrin.h>
#endif
// ----------------------------------------------------------------------
// constexpr if
// ----------------------------------------------------------------------
#if PHMAP_HAVE_CC17
#define PHMAP_IF_CONSTEXPR(expr) if constexpr ((expr))
#else
#define PHMAP_IF_CONSTEXPR(expr) if ((expr))
#endif
// ----------------------------------------------------------------------
// base/macros.h
// ----------------------------------------------------------------------
// PHMAP_ARRAYSIZE()
//
// Returns the number of elements in an array as a compile-time constant, which
// can be used in defining new arrays. If you use this macro on a pointer by
// mistake, you will get a compile-time error.
#define PHMAP_ARRAYSIZE(array) \
(sizeof(::phmap::macros_internal::ArraySizeHelper(array)))
namespace phmap {
namespace macros_internal {
// Note: this internal template function declaration is used by PHMAP_ARRAYSIZE.
// The function doesn't need a definition, as we only use its type.
template <typename T, size_t N>
auto ArraySizeHelper(const T (&array)[N]) -> char (&)[N];
} // namespace macros_internal
} // namespace phmap
// TODO(zhangxy): Use c++17 standard [[fallthrough]] macro, when supported.
#if defined(__clang__) && defined(__has_warning)
#if __has_feature(cxx_attributes) && __has_warning("-Wimplicit-fallthrough")
#define PHMAP_FALLTHROUGH_INTENDED [[clang::fallthrough]]
#endif
#elif defined(__GNUC__) && __GNUC__ >= 7
#define PHMAP_FALLTHROUGH_INTENDED [[gnu::fallthrough]]
#endif
#ifndef PHMAP_FALLTHROUGH_INTENDED
#define PHMAP_FALLTHROUGH_INTENDED \
do { } while (0)
#endif
// PHMAP_DEPRECATED()
//
// Marks a deprecated class, struct, enum, function, method and variable
// declarations. The macro argument is used as a custom diagnostic message (e.g.
// suggestion of a better alternative).
//
// Example:
//
// class PHMAP_DEPRECATED("Use Bar instead") Foo {...};
// PHMAP_DEPRECATED("Use Baz instead") void Bar() {...}
//
// Every usage of a deprecated entity will trigger a warning when compiled with
// clang's `-Wdeprecated-declarations` option. This option is turned off by
// default, but the warnings will be reported by clang-tidy.
#if defined(__clang__) && __cplusplus >= 201103L
#define PHMAP_DEPRECATED(message) __attribute__((deprecated(message)))
#endif
#ifndef PHMAP_DEPRECATED
#define PHMAP_DEPRECATED(message)
#endif
// PHMAP_BAD_CALL_IF()
//
// Used on a function overload to trap bad calls: any call that matches the
// overload will cause a compile-time error. This macro uses a clang-specific
// "enable_if" attribute, as described at
// http://clang.llvm.org/docs/AttributeReference.html#enable-if
//
// Overloads which use this macro should be bracketed by
// `#ifdef PHMAP_BAD_CALL_IF`.
//
// Example:
//
// int isdigit(int c);
// #ifdef PHMAP_BAD_CALL_IF
// int isdigit(int c)
// PHMAP_BAD_CALL_IF(c <= -1 || c > 255,
// "'c' must have the value of an unsigned char or EOF");
// #endif // PHMAP_BAD_CALL_IF
#if defined(__clang__)
#if __has_attribute(enable_if)
#define PHMAP_BAD_CALL_IF(expr, msg) \
__attribute__((enable_if(expr, "Bad call trap"), unavailable(msg)))
#endif
#endif
// PHMAP_ASSERT()
//
// In C++11, `assert` can't be used portably within constexpr functions.
// PHMAP_ASSERT functions as a runtime assert but works in C++11 constexpr
// functions. Example:
//
// constexpr double Divide(double a, double b) {
// return PHMAP_ASSERT(b != 0), a / b;
// }
//
// This macro is inspired by
// https://akrzemi1.wordpress.com/2017/05/18/asserts-in-constexpr-functions/
#if defined(NDEBUG)
#define PHMAP_ASSERT(expr) (false ? (void)(expr) : (void)0)
#else
#define PHMAP_ASSERT(expr) \
(PHMAP_PREDICT_TRUE((expr)) ? (void)0 \
: [] { assert(false && #expr); }()) // NOLINT
#endif
#ifdef PHMAP_HAVE_EXCEPTIONS
#define PHMAP_INTERNAL_TRY try
#define PHMAP_INTERNAL_CATCH_ANY catch (...)
#define PHMAP_INTERNAL_RETHROW do { throw; } while (false)
#else // PHMAP_HAVE_EXCEPTIONS
#define PHMAP_INTERNAL_TRY if (true)
#define PHMAP_INTERNAL_CATCH_ANY else if (false)
#define PHMAP_INTERNAL_RETHROW do {} while (false)
#endif // PHMAP_HAVE_EXCEPTIONS
#endif // phmap_config_h_guard_

View file

@ -0,0 +1,312 @@
#if !defined(phmap_dump_h_guard_)
#define phmap_dump_h_guard_
// ---------------------------------------------------------------------------
// Copyright (c) 2019, Gregory Popovitch - greg7mdp@gmail.com
//
// providing dump/load/mmap_load
//
// 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
//
// https://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
// limitations under the License.
// ---------------------------------------------------------------------------
#include <iostream>
#include <fstream>
#include <sstream>
#include "phmap.h"
namespace phmap
{
namespace type_traits_internal {
#if defined(__GLIBCXX__) && __GLIBCXX__ < 20150801
template<typename T> struct IsTriviallyCopyable : public std::integral_constant<bool, __has_trivial_copy(T)> {};
#else
template<typename T> struct IsTriviallyCopyable : public std::is_trivially_copyable<T> {};
#endif
template <class T1, class T2>
struct IsTriviallyCopyable<std::pair<T1, T2>> {
static constexpr bool value = IsTriviallyCopyable<T1>::value && IsTriviallyCopyable<T2>::value;
};
}
namespace priv {
#if !defined(PHMAP_NON_DETERMINISTIC) && !defined(PHMAP_DISABLE_DUMP)
static constexpr size_t s_version_base = std::numeric_limits<size_t>::max() - 10;
static constexpr size_t s_version = s_version_base;
// ------------------------------------------------------------------------
// dump/load for raw_hash_set
// ------------------------------------------------------------------------
template <class Policy, class Hash, class Eq, class Alloc>
template<typename OutputArchive>
bool raw_hash_set<Policy, Hash, Eq, Alloc>::phmap_dump(OutputArchive& ar) const {
static_assert(type_traits_internal::IsTriviallyCopyable<value_type>::value,
"value_type should be trivially copyable");
ar.saveBinary(&s_version, sizeof(size_t));
ar.saveBinary(&size_, sizeof(size_t));
ar.saveBinary(&capacity_, sizeof(size_t));
if (size_ == 0)
return true;
ar.saveBinary(ctrl_, sizeof(ctrl_t) * (capacity_ + Group::kWidth + 1));
ar.saveBinary(slots_, sizeof(slot_type) * capacity_);
ar.saveBinary(&growth_left(), sizeof(size_t));
return true;
}
template <class Policy, class Hash, class Eq, class Alloc>
template<typename InputArchive>
bool raw_hash_set<Policy, Hash, Eq, Alloc>::phmap_load(InputArchive& ar) {
static_assert(type_traits_internal::IsTriviallyCopyable<value_type>::value,
"value_type should be trivially copyable");
raw_hash_set<Policy, Hash, Eq, Alloc>().swap(*this); // clear any existing content
size_t version = 0;
ar.loadBinary(&version, sizeof(size_t));
if (version < s_version_base) {
// we didn't store the version, version actually contains the size
size_ = version;
} else {
ar.loadBinary(&size_, sizeof(size_t));
}
ar.loadBinary(&capacity_, sizeof(size_t));
if (capacity_) {
// allocate memory for ctrl_ and slots_
initialize_slots(capacity_);
}
if (size_ == 0)
return true;
ar.loadBinary(ctrl_, sizeof(ctrl_t) * (capacity_ + Group::kWidth + 1));
ar.loadBinary(slots_, sizeof(slot_type) * capacity_);
if (version >= s_version_base) {
// growth_left should be restored after calling initialize_slots() which resets it.
ar.loadBinary(&growth_left(), sizeof(size_t));
}
return true;
}
// ------------------------------------------------------------------------
// dump/load for parallel_hash_set
// ------------------------------------------------------------------------
template <size_t N,
template <class, class, class, class> class RefSet,
class Mtx_,
class Policy, class Hash, class Eq, class Alloc>
template<typename OutputArchive>
bool parallel_hash_set<N, RefSet, Mtx_, Policy, Hash, Eq, Alloc>::phmap_dump(OutputArchive& ar) const {
static_assert(type_traits_internal::IsTriviallyCopyable<value_type>::value,
"value_type should be trivially copyable");
size_t submap_count = subcnt();
ar.saveBinary(&submap_count, sizeof(size_t));
for (size_t i = 0; i < sets_.size(); ++i) {
auto& inner = sets_[i];
typename Lockable::UniqueLock m(const_cast<Inner&>(inner));
if (!inner.set_.phmap_dump(ar)) {
std::cerr << "Failed to dump submap " << i << std::endl;
return false;
}
}
return true;
}
template <size_t N,
template <class, class, class, class> class RefSet,
class Mtx_,
class Policy, class Hash, class Eq, class Alloc>
template<typename InputArchive>
bool parallel_hash_set<N, RefSet, Mtx_, Policy, Hash, Eq, Alloc>::phmap_load(InputArchive& ar) {
static_assert(type_traits_internal::IsTriviallyCopyable<value_type>::value,
"value_type should be trivially copyable");
size_t submap_count = 0;
ar.loadBinary(&submap_count, sizeof(size_t));
if (submap_count != subcnt()) {
std::cerr << "submap count(" << submap_count << ") != N(" << N << ")" << std::endl;
return false;
}
for (size_t i = 0; i < submap_count; ++i) {
auto& inner = sets_[i];
typename Lockable::UniqueLock m(const_cast<Inner&>(inner));
if (!inner.set_.phmap_load(ar)) {
std::cerr << "Failed to load submap " << i << std::endl;
return false;
}
}
return true;
}
#endif // !defined(PHMAP_NON_DETERMINISTIC) && !defined(PHMAP_DISABLE_DUMP)
} // namespace priv
// ------------------------------------------------------------------------
// BinaryArchive
// File is closed when archive object is destroyed
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
class BinaryOutputArchive {
public:
BinaryOutputArchive(const char *file_path) {
ofs_.open(file_path, std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
}
~BinaryOutputArchive() = default;
BinaryOutputArchive(const BinaryOutputArchive&) = delete;
BinaryOutputArchive& operator=(const BinaryOutputArchive&) = delete;
bool saveBinary(const void *p, size_t sz) {
ofs_.write(reinterpret_cast<const char*>(p), (std::streamsize)sz);
return true;
}
template<typename V>
typename std::enable_if<type_traits_internal::IsTriviallyCopyable<V>::value, bool>::type
saveBinary(const V& v) {
ofs_.write(reinterpret_cast<const char *>(&v), sizeof(V));
return true;
}
template<typename Map>
auto saveBinary(const Map& v) -> decltype(v.phmap_dump(*this), bool())
{
return v.phmap_dump(*this);
}
private:
std::ofstream ofs_;
};
class BinaryInputArchive {
public:
BinaryInputArchive(const char * file_path) {
ifs_.open(file_path, std::ofstream::in | std::ofstream::binary);
}
~BinaryInputArchive() = default;
BinaryInputArchive(const BinaryInputArchive&) = delete;
BinaryInputArchive& operator=(const BinaryInputArchive&) = delete;
bool loadBinary(void* p, size_t sz) {
ifs_.read(reinterpret_cast<char*>(p), (std::streamsize)sz);
return true;
}
template<typename V>
typename std::enable_if<type_traits_internal::IsTriviallyCopyable<V>::value, bool>::type
loadBinary(V* v) {
ifs_.read(reinterpret_cast<char *>(v), sizeof(V));
return true;
}
template<typename Map>
auto loadBinary(Map* v) -> decltype(v->phmap_load(*this), bool())
{
return v->phmap_load(*this);
}
private:
std::ifstream ifs_;
};
} // namespace phmap
#ifdef CEREAL_SIZE_TYPE
template <class T>
using PhmapTrivCopyable = typename phmap::type_traits_internal::IsTriviallyCopyable<T>;
namespace cereal
{
// Overload Cereal serialization code for phmap::flat_hash_map
// -----------------------------------------------------------
template <class K, class V, class Hash, class Eq, class A>
void save(typename std::enable_if<PhmapTrivCopyable<K>::value && PhmapTrivCopyable<V>::value, typename cereal::BinaryOutputArchive>::type &ar,
phmap::flat_hash_map<K, V, Hash, Eq, A> const &hmap)
{
hmap.phmap_dump(ar);
}
template <class K, class V, class Hash, class Eq, class A>
void load(typename std::enable_if<PhmapTrivCopyable<K>::value && PhmapTrivCopyable<V>::value, typename cereal::BinaryInputArchive>::type &ar,
phmap::flat_hash_map<K, V, Hash, Eq, A> &hmap)
{
hmap.phmap_load(ar);
}
// Overload Cereal serialization code for phmap::parallel_flat_hash_map
// --------------------------------------------------------------------
template <class K, class V, class Hash, class Eq, class A, size_t N, class Mtx_>
void save(typename std::enable_if<PhmapTrivCopyable<K>::value && PhmapTrivCopyable<V>::value, typename cereal::BinaryOutputArchive>::type &ar,
phmap::parallel_flat_hash_map<K, V, Hash, Eq, A, N, Mtx_> const &hmap)
{
hmap.phmap_dump(ar);
}
template <class K, class V, class Hash, class Eq, class A, size_t N, class Mtx_>
void load(typename std::enable_if<PhmapTrivCopyable<K>::value && PhmapTrivCopyable<V>::value, typename cereal::BinaryInputArchive>::type &ar,
phmap::parallel_flat_hash_map<K, V, Hash, Eq, A, N, Mtx_> &hmap)
{
hmap.phmap_load(ar);
}
// Overload Cereal serialization code for phmap::flat_hash_set
// -----------------------------------------------------------
template <class K, class Hash, class Eq, class A>
void save(typename std::enable_if<PhmapTrivCopyable<K>::value, typename cereal::BinaryOutputArchive>::type &ar,
phmap::flat_hash_set<K, Hash, Eq, A> const &hset)
{
hset.phmap_dump(ar);
}
template <class K, class Hash, class Eq, class A>
void load(typename std::enable_if<PhmapTrivCopyable<K>::value, typename cereal::BinaryInputArchive>::type &ar,
phmap::flat_hash_set<K, Hash, Eq, A> &hset)
{
hset.phmap_load(ar);
}
// Overload Cereal serialization code for phmap::parallel_flat_hash_set
// --------------------------------------------------------------------
template <class K, class Hash, class Eq, class A, size_t N, class Mtx_>
void save(typename std::enable_if<PhmapTrivCopyable<K>::value, typename cereal::BinaryOutputArchive>::type &ar,
phmap::parallel_flat_hash_set<K, Hash, Eq, A, N, Mtx_> const &hset)
{
hset.phmap_dump(ar);
}
template <class K, class Hash, class Eq, class A, size_t N, class Mtx_>
void load(typename std::enable_if<PhmapTrivCopyable<K>::value, typename cereal::BinaryInputArchive>::type &ar,
phmap::parallel_flat_hash_set<K, Hash, Eq, A, N, Mtx_> &hset)
{
hset.phmap_load(ar);
}
}
#endif
#endif // phmap_dump_h_guard_

View file

@ -0,0 +1,186 @@
#if !defined(phmap_fwd_decl_h_guard_)
#define phmap_fwd_decl_h_guard_
// ---------------------------------------------------------------------------
// Copyright (c) 2019, Gregory Popovitch - greg7mdp@gmail.com
//
// 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
//
// https://www.apache.org/licenses/LICENSE-2.0
// ---------------------------------------------------------------------------
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4514) // unreferenced inline function has been removed
#pragma warning(disable : 4710) // function not inlined
#pragma warning(disable : 4711) // selected for automatic inline expansion
#endif
#include <memory>
#include <utility>
#include <mutex>
#if defined(PHMAP_USE_ABSL_HASH) && !defined(ABSL_HASH_HASH_H_)
namespace absl { template <class T> struct Hash; };
#endif
namespace phmap {
#if defined(PHMAP_USE_ABSL_HASH)
template <class T> using Hash = ::absl::Hash<T>;
#else
template <class T> struct Hash;
#endif
template <class T> struct EqualTo;
template <class T> struct Less;
template <class T> using Allocator = typename std::allocator<T>;
template<class T1, class T2> using Pair = typename std::pair<T1, T2>;
class NullMutex;
namespace priv {
// The hash of an object of type T is computed by using phmap::Hash.
template <class T, class E = void>
struct HashEq
{
using Hash = phmap::Hash<T>;
using Eq = phmap::EqualTo<T>;
};
template <class T>
using hash_default_hash = typename priv::HashEq<T>::Hash;
template <class T>
using hash_default_eq = typename priv::HashEq<T>::Eq;
// type alias for std::allocator so we can forward declare without including other headers
template <class T>
using Allocator = typename phmap::Allocator<T>;
// type alias for std::pair so we can forward declare without including other headers
template<class T1, class T2>
using Pair = typename phmap::Pair<T1, T2>;
} // namespace priv
// ------------- forward declarations for hash containers ----------------------------------
template <class T,
class Hash = phmap::priv::hash_default_hash<T>,
class Eq = phmap::priv::hash_default_eq<T>,
class Alloc = phmap::priv::Allocator<T>> // alias for std::allocator
class flat_hash_set;
template <class K, class V,
class Hash = phmap::priv::hash_default_hash<K>,
class Eq = phmap::priv::hash_default_eq<K>,
class Alloc = phmap::priv::Allocator<
phmap::priv::Pair<const K, V>>> // alias for std::allocator
class flat_hash_map;
template <class T,
class Hash = phmap::priv::hash_default_hash<T>,
class Eq = phmap::priv::hash_default_eq<T>,
class Alloc = phmap::priv::Allocator<T>> // alias for std::allocator
class node_hash_set;
template <class Key, class Value,
class Hash = phmap::priv::hash_default_hash<Key>,
class Eq = phmap::priv::hash_default_eq<Key>,
class Alloc = phmap::priv::Allocator<
phmap::priv::Pair<const Key, Value>>> // alias for std::allocator
class node_hash_map;
template <class T,
class Hash = phmap::priv::hash_default_hash<T>,
class Eq = phmap::priv::hash_default_eq<T>,
class Alloc = phmap::priv::Allocator<T>, // alias for std::allocator
size_t N = 4, // 2**N submaps
class Mutex = phmap::NullMutex> // use std::mutex to enable internal locks
class parallel_flat_hash_set;
template <class K, class V,
class Hash = phmap::priv::hash_default_hash<K>,
class Eq = phmap::priv::hash_default_eq<K>,
class Alloc = phmap::priv::Allocator<
phmap::priv::Pair<const K, V>>, // alias for std::allocator
size_t N = 4, // 2**N submaps
class Mutex = phmap::NullMutex> // use std::mutex to enable internal locks
class parallel_flat_hash_map;
template <class T,
class Hash = phmap::priv::hash_default_hash<T>,
class Eq = phmap::priv::hash_default_eq<T>,
class Alloc = phmap::priv::Allocator<T>, // alias for std::allocator
size_t N = 4, // 2**N submaps
class Mutex = phmap::NullMutex> // use std::mutex to enable internal locks
class parallel_node_hash_set;
template <class Key, class Value,
class Hash = phmap::priv::hash_default_hash<Key>,
class Eq = phmap::priv::hash_default_eq<Key>,
class Alloc = phmap::priv::Allocator<
phmap::priv::Pair<const Key, Value>>, // alias for std::allocator
size_t N = 4, // 2**N submaps
class Mutex = phmap::NullMutex> // use std::mutex to enable internal locks
class parallel_node_hash_map;
// -----------------------------------------------------------------------------
// phmap::parallel_*_hash_* using std::mutex by default
// -----------------------------------------------------------------------------
template <class T,
class Hash = phmap::priv::hash_default_hash<T>,
class Eq = phmap::priv::hash_default_eq<T>,
class Alloc = phmap::priv::Allocator<T>,
size_t N = 4>
using parallel_flat_hash_set_m = parallel_flat_hash_set<T, Hash, Eq, Alloc, N, std::mutex>;
template <class K, class V,
class Hash = phmap::priv::hash_default_hash<K>,
class Eq = phmap::priv::hash_default_eq<K>,
class Alloc = phmap::priv::Allocator<phmap::priv::Pair<const K, V>>,
size_t N = 4>
using parallel_flat_hash_map_m = parallel_flat_hash_map<K, V, Hash, Eq, Alloc, N, std::mutex>;
template <class T,
class Hash = phmap::priv::hash_default_hash<T>,
class Eq = phmap::priv::hash_default_eq<T>,
class Alloc = phmap::priv::Allocator<T>,
size_t N = 4>
using parallel_node_hash_set_m = parallel_node_hash_set<T, Hash, Eq, Alloc, N, std::mutex>;
template <class K, class V,
class Hash = phmap::priv::hash_default_hash<K>,
class Eq = phmap::priv::hash_default_eq<K>,
class Alloc = phmap::priv::Allocator<phmap::priv::Pair<const K, V>>,
size_t N = 4>
using parallel_node_hash_map_m = parallel_node_hash_map<K, V, Hash, Eq, Alloc, N, std::mutex>;
// ------------- forward declarations for btree containers ----------------------------------
template <typename Key, typename Compare = phmap::Less<Key>,
typename Alloc = phmap::Allocator<Key>>
class btree_set;
template <typename Key, typename Compare = phmap::Less<Key>,
typename Alloc = phmap::Allocator<Key>>
class btree_multiset;
template <typename Key, typename Value, typename Compare = phmap::Less<Key>,
typename Alloc = phmap::Allocator<phmap::priv::Pair<const Key, Value>>>
class btree_map;
template <typename Key, typename Value, typename Compare = phmap::Less<Key>,
typename Alloc = phmap::Allocator<phmap::priv::Pair<const Key, Value>>>
class btree_multimap;
} // namespace phmap
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif // phmap_fwd_decl_h_guard_

View file

@ -0,0 +1,407 @@
#if !defined(phmap_utils_h_guard_)
#define phmap_utils_h_guard_
// ---------------------------------------------------------------------------
// Copyright (c) 2019, Gregory Popovitch - greg7mdp@gmail.com
//
// minimal header providing phmap::HashState
//
// use as: phmap::HashState().combine(0, _first_name, _last_name, _age);
//
// 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
//
// https://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
// limitations under the License.
// ---------------------------------------------------------------------------
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4514) // unreferenced inline function has been removed
#pragma warning(disable : 4710) // function not inlined
#pragma warning(disable : 4711) // selected for automatic inline expansion
#endif
#include <cstdint>
#include <functional>
#include <tuple>
#include "phmap_bits.h"
// ---------------------------------------------------------------
// Absl forward declaration requires global scope.
// ---------------------------------------------------------------
#if defined(PHMAP_USE_ABSL_HASH) && !defined(phmap_fwd_decl_h_guard_) && !defined(ABSL_HASH_HASH_H_)
namespace absl { template <class T> struct Hash; };
#endif
namespace phmap
{
// ---------------------------------------------------------------
// ---------------------------------------------------------------
template<int n>
struct phmap_mix
{
inline size_t operator()(size_t) const;
};
template<>
struct phmap_mix<4>
{
inline size_t operator()(size_t a) const
{
static constexpr uint64_t kmul = 0xcc9e2d51UL;
uint64_t l = a * kmul;
return static_cast<size_t>(l ^ (l >> 32));
}
};
#if defined(PHMAP_HAS_UMUL128)
template<>
struct phmap_mix<8>
{
// Very fast mixing (similar to Abseil)
inline size_t operator()(size_t a) const
{
static constexpr uint64_t k = 0xde5fb9d2630458e9ULL;
uint64_t h;
uint64_t l = umul128(a, k, &h);
return static_cast<size_t>(h + l);
}
};
#else
template<>
struct phmap_mix<8>
{
inline size_t operator()(size_t a) const
{
a = (~a) + (a << 21); // a = (a << 21) - a - 1;
a = a ^ (a >> 24);
a = (a + (a << 3)) + (a << 8); // a * 265
a = a ^ (a >> 14);
a = (a + (a << 2)) + (a << 4); // a * 21
a = a ^ (a >> 28);
a = a + (a << 31);
return static_cast<size_t>(a);
}
};
#endif
// --------------------------------------------
template<int n>
struct fold_if_needed
{
inline size_t operator()(uint64_t) const;
};
template<>
struct fold_if_needed<4>
{
inline size_t operator()(uint64_t a) const
{
return static_cast<size_t>(a ^ (a >> 32));
}
};
template<>
struct fold_if_needed<8>
{
inline size_t operator()(uint64_t a) const
{
return static_cast<size_t>(a);
}
};
// ---------------------------------------------------------------
// see if class T has a hash_value() friend method
// ---------------------------------------------------------------
template<typename T>
struct has_hash_value
{
private:
typedef std::true_type yes;
typedef std::false_type no;
template<typename U> static auto test(int) -> decltype(hash_value(std::declval<const U&>()) == 1, yes());
template<typename> static no test(...);
public:
static constexpr bool value = std::is_same<decltype(test<T>(0)), yes>::value;
};
#if defined(PHMAP_USE_ABSL_HASH) && !defined(phmap_fwd_decl_h_guard_)
template <class T> using Hash = ::absl::Hash<T>;
#elif !defined(PHMAP_USE_ABSL_HASH)
// ---------------------------------------------------------------
// phmap::Hash
// ---------------------------------------------------------------
template <class T>
struct Hash
{
template <class U, typename std::enable_if<has_hash_value<U>::value, int>::type = 0>
size_t _hash(const T& val) const
{
return hash_value(val);
}
template <class U, typename std::enable_if<!has_hash_value<U>::value, int>::type = 0>
size_t _hash(const T& val) const
{
return std::hash<T>()(val);
}
inline size_t operator()(const T& val) const
{
return _hash<T>(val);
}
};
template<class ArgumentType, class ResultType>
struct phmap_unary_function
{
typedef ArgumentType argument_type;
typedef ResultType result_type;
};
template <>
struct Hash<bool> : public phmap_unary_function<bool, size_t>
{
inline size_t operator()(bool val) const noexcept
{ return static_cast<size_t>(val); }
};
template <>
struct Hash<char> : public phmap_unary_function<char, size_t>
{
inline size_t operator()(char val) const noexcept
{ return static_cast<size_t>(val); }
};
template <>
struct Hash<signed char> : public phmap_unary_function<signed char, size_t>
{
inline size_t operator()(signed char val) const noexcept
{ return static_cast<size_t>(val); }
};
template <>
struct Hash<unsigned char> : public phmap_unary_function<unsigned char, size_t>
{
inline size_t operator()(unsigned char val) const noexcept
{ return static_cast<size_t>(val); }
};
#ifdef PHMAP_HAS_NATIVE_WCHAR_T
template <>
struct Hash<wchar_t> : public phmap_unary_function<wchar_t, size_t>
{
inline size_t operator()(wchar_t val) const noexcept
{ return static_cast<size_t>(val); }
};
#endif
template <>
struct Hash<int16_t> : public phmap_unary_function<int16_t, size_t>
{
inline size_t operator()(int16_t val) const noexcept
{ return static_cast<size_t>(val); }
};
template <>
struct Hash<uint16_t> : public phmap_unary_function<uint16_t, size_t>
{
inline size_t operator()(uint16_t val) const noexcept
{ return static_cast<size_t>(val); }
};
template <>
struct Hash<int32_t> : public phmap_unary_function<int32_t, size_t>
{
inline size_t operator()(int32_t val) const noexcept
{ return static_cast<size_t>(val); }
};
template <>
struct Hash<uint32_t> : public phmap_unary_function<uint32_t, size_t>
{
inline size_t operator()(uint32_t val) const noexcept
{ return static_cast<size_t>(val); }
};
template <>
struct Hash<int64_t> : public phmap_unary_function<int64_t, size_t>
{
inline size_t operator()(int64_t val) const noexcept
{ return fold_if_needed<sizeof(size_t)>()(static_cast<uint64_t>(val)); }
};
template <>
struct Hash<uint64_t> : public phmap_unary_function<uint64_t, size_t>
{
inline size_t operator()(uint64_t val) const noexcept
{ return fold_if_needed<sizeof(size_t)>()(val); }
};
template <>
struct Hash<float> : public phmap_unary_function<float, size_t>
{
inline size_t operator()(float val) const noexcept
{
// -0.0 and 0.0 should return same hash
uint32_t *as_int = reinterpret_cast<uint32_t *>(&val);
return (val == 0) ? static_cast<size_t>(0) :
static_cast<size_t>(*as_int);
}
};
template <>
struct Hash<double> : public phmap_unary_function<double, size_t>
{
inline size_t operator()(double val) const noexcept
{
// -0.0 and 0.0 should return same hash
uint64_t *as_int = reinterpret_cast<uint64_t *>(&val);
return (val == 0) ? static_cast<size_t>(0) :
fold_if_needed<sizeof(size_t)>()(*as_int);
}
};
#endif
#if defined(_MSC_VER)
# define PHMAP_HASH_ROTL32(x, r) _rotl(x,r)
#else
# define PHMAP_HASH_ROTL32(x, r) (x << r) | (x >> (32 - r))
#endif
template <class H, int sz> struct Combiner
{
H operator()(H seed, size_t value);
};
template <class H> struct Combiner<H, 4>
{
H operator()(H h1, size_t k1)
{
// Copyright 2005-2014 Daniel James.
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
const uint32_t c1 = 0xcc9e2d51;
const uint32_t c2 = 0x1b873593;
k1 *= c1;
k1 = PHMAP_HASH_ROTL32(k1,15);
k1 *= c2;
h1 ^= k1;
h1 = PHMAP_HASH_ROTL32(h1,13);
h1 = h1*5+0xe6546b64;
return h1;
}
};
template <class H> struct Combiner<H, 8>
{
H operator()(H h, size_t k)
{
// Copyright 2005-2014 Daniel James.
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
const uint64_t m = (uint64_t(0xc6a4a793) << 32) + 0x5bd1e995;
const int r = 47;
k *= m;
k ^= k >> r;
k *= m;
h ^= k;
h *= m;
// Completely arbitrary number, to prevent 0's
// from hashing to 0.
h += 0xe6546b64;
return h;
}
};
// define HashState to combine member hashes... see example below
// -----------------------------------------------------------------------------
template <typename H>
class HashStateBase {
public:
template <typename T, typename... Ts>
static H combine(H state, const T& value, const Ts&... values);
static H combine(H state) { return state; }
};
template <typename H>
template <typename T, typename... Ts>
H HashStateBase<H>::combine(H seed, const T& v, const Ts&... vs)
{
return HashStateBase<H>::combine(Combiner<H, sizeof(H)>()(
seed, phmap::Hash<T>()(v)),
vs...);
}
using HashState = HashStateBase<size_t>;
// -----------------------------------------------------------------------------
#if !defined(PHMAP_USE_ABSL_HASH)
// define Hash for std::pair
// -------------------------
template<class T1, class T2>
struct Hash<std::pair<T1, T2>> {
size_t operator()(std::pair<T1, T2> const& p) const noexcept {
return phmap::HashState().combine(phmap::Hash<T1>()(p.first), p.second);
}
};
// define Hash for std::tuple
// --------------------------
template<class... T>
struct Hash<std::tuple<T...>> {
size_t operator()(std::tuple<T...> const& t) const noexcept {
size_t seed = 0;
return _hash_helper(seed, t);
}
private:
template<size_t I = 0, class TUP>
typename std::enable_if<I == std::tuple_size<TUP>::value, size_t>::type
_hash_helper(size_t seed, const TUP &) const noexcept { return seed; }
template<size_t I = 0, class TUP>
typename std::enable_if<I < std::tuple_size<TUP>::value, size_t>::type
_hash_helper(size_t seed, const TUP &t) const noexcept {
const auto &el = std::get<I>(t);
using el_type = typename std::remove_cv<typename std::remove_reference<decltype(el)>::type>::type;
seed = Combiner<size_t, sizeof(size_t)>()(seed, phmap::Hash<el_type>()(el));
return _hash_helper<I + 1>(seed, t);
}
};
#endif
} // namespace phmap
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif // phmap_utils_h_guard_

14
include/version.h Normal file
View file

@ -0,0 +1,14 @@
#ifndef ENDGAME_ANALYZER_VERSION_H
#define ENDGAME_ANALYZER_VERSION_H
#include <string>
namespace Version {
extern const std::string git_sha1;
extern const std::string git_description;
extern const std::string git_refspec;
extern const std::string git_local_changes;
bool is_dirty();
}
#endif //ENDGAME_ANALYZER_VERSION_H

View file

@ -4,6 +4,8 @@
#include "null_buffer.h"
#include "state_explorer.h"
#include <version.h>
#include "command_line_interface.h"
#include "myassert.h"
@ -36,6 +38,15 @@ namespace Hanabi
int run_cli(CLIParms const & parms)
{
if (parms.version_info) {
std::cout << "endgame-analyzer " << Version::git_description << " (commit ";
std::cout.write(Version::git_sha1.data(), 8) << ")" << std::endl;
if (Version::is_dirty())
{
std::cout << "Warning: The repository contains local changes that are not part of the build." << std::endl;
}
return EXIT_SUCCESS;
}
// We want to do this sanity check here again,
// so that the run_cli method itself can ensure that arguments are fully valid
// and we cannot run into crashes due to bad specified parameters
@ -55,11 +66,11 @@ namespace Hanabi
Game game = [&parms] {
if (std::holds_alternative<int>(parms.game))
{
return Download::get_game(std::get<int>(parms.game), convert_optional(parms.score_goal));
return Download::get_game(std::get<int>(parms.game), convert_optional(parms.score_goal), parms.save_memory);
}
else
{
return Download::get_game(std::get<std::string>(parms.game), convert_optional(parms.score_goal));
return Download::get_game(std::get<std::string>(parms.game), convert_optional(parms.score_goal), parms.save_memory);
}
}();
@ -209,12 +220,12 @@ namespace Hanabi
quiet_os << "Visited " << game.state->enumerated_states() << " states." << std::endl;
quiet_os << "Enumerated " << game.state->position_tablebase().size() << " unique game states. " << std::endl;
// If specified, we can now launch the interactive shell
if (parms.interactive.value_or(!parms.quiet))
{
quiet_os << "\nDropping into interactive command line to explore result (type 'help'):" << std::endl;
cli(game);
}
}
// If specified, we can now launch the interactive shell
if (parms.interactive.value_or(!parms.quiet))
{
quiet_os << "\nDropping into interactive command line to explore result (type 'help'):" << std::endl;
cli(game);
}
return EXIT_SUCCESS;
}
@ -229,9 +240,9 @@ namespace Hanabi
("file,f", bpo::value<std::string>(), "Input file containing game in hanab.live json format.")
("turn,t", bpo::value<unsigned>(&parms.game_state_spec), "Turn number of state to analyze. "
"Turn 1 means no actions have been taken. ")
("draw-pile-size,d", bpo::value<unsigned>(&parms.game_state_spec), "Draw pile size of state to analyze.")
("draw-pile-size,d", bpo::value<unsigned>(&parms.game_state_spec), "Draw pile size of state to analyze. Must be at most 20.")
("score-goal,s"
, bpo::value<boost::optional<uint8_t>>(&parms.score_goal)
, bpo::value<boost::optional<unsigned int>>(&parms.score_goal)
, "Score that counts as a win, i.e. is optimized for achieving. If unspecified, the maximum possible "
"score will be used.")
("clue-modifier,c", bpo::value<int>(), "Optional relative modification to the number of clues applied to "
@ -246,12 +257,20 @@ namespace Hanabi
("list-actions,l","List all actions (including suboptimal ones) of all turns after specified state.")
("all-clues", "Whenever evaluating a game state, evaluate it with all clue counts and output their "
"probabilities.")
("quiet,q", "Deactivate all non-essential prints. Useful if output is parsed by another program.");
("save-memory", "Reduce memory consumption by roughly 50%. This results in roughly 5 times as much execution time, so use this only if you are really short on memory.")
("quiet,q", "Deactivate all non-essential prints. Useful if output is parsed by another program.")
("version,v", "Print version information and exit.");
bpo::variables_map vm;
bpo::store(bpo::parse_command_line(argc, argv, desc), vm);
bpo::notify(vm);
if (vm.count("version"))
{
parms.version_info = true;
return parms;
}
if (vm.count("help"))
{
std::cout << "This program performs endgame analysis of Hanabi. It calculates optimum strategies\n"
@ -267,7 +286,6 @@ namespace Hanabi
std::cout << "You have to either specify --game or --file as a data source.\n";
std::cout << "You may not specify both --turn and --draw at the same time.\n";
std::cout << "You may not specifiy both --recursive and --list-actions at the same time.\n";
std::cout << "If none of them is specified, it is assumed that --draw 5 was given." << std::endl;
return std::nullopt;
}
@ -331,6 +349,7 @@ namespace Hanabi
parms.recursive = vm.count("recursive") > 0;
parms.list_actions = vm.count("list-actions") > 0;
parms.quiet = vm.count("quiet") > 0;
parms.save_memory = vm.count("save-memory") > 0;
if (parms.recursive and std::holds_alternative<clue_t>(parms.clue_spec) and std::get<clue_t>(parms.clue_spec) != clue_t(0))
{

View file

@ -32,7 +32,7 @@ namespace Download
return boost::json::parse(game_json).as_object();
}
Hanabi::Game get_game(int game_id, std::optional<uint8_t> score_goal)
Hanabi::Game get_game(int game_id, std::optional<uint8_t> score_goal, bool save_memory)
{
std::optional<boost::json::object> const game_json = download_game_json(game_id);
if (!game_json.has_value() or game_json.value().empty())
@ -42,10 +42,15 @@ namespace Download
Hanabi::GameInfo game_info = Parsing::parse_game(game_json.value());
return {make_game_state(game_info.num_suits, game_info.num_players, game_info.deck, game_info.num_clues_gained_per_discard_or_stack_finished, score_goal), game_info};
Hanabi::HanabiStateConfig config;
config.num_clues_gained_on_discard_or_stack_finished = game_info.num_clues_gained_per_discard_or_stack_finished;
config.save_memory = save_memory;
config.score_goal = score_goal;
return {make_game_state(game_info.num_suits, game_info.num_players, game_info.deck, config), game_info};
}
Hanabi::Game get_game(std::string const & filename, std::optional<uint8_t> score_goal)
Hanabi::Game get_game(std::string const & filename, std::optional<uint8_t> score_goal, bool save_memory)
{
std::optional<boost::json::object> const game_json = open_game_json(filename.c_str());
if (!game_json.has_value() or game_json.value().empty())
@ -54,6 +59,12 @@ namespace Download
}
Hanabi::GameInfo game_info = Parsing::parse_game(game_json.value());
return {make_game_state(game_info.num_suits, game_info.num_players, game_info.deck, game_info.num_clues_gained_per_discard_or_stack_finished, score_goal), game_info};
Hanabi::HanabiStateConfig config;
config.num_clues_gained_on_discard_or_stack_finished = game_info.num_clues_gained_per_discard_or_stack_finished;
config.save_memory = save_memory;
config.score_goal = score_goal;
return {make_game_state(game_info.num_suits, game_info.num_players, game_info.deck, config), game_info};
}
} // namespace Download

View file

@ -119,8 +119,8 @@ namespace Hanabi
{
make_turn();
}
while (state->draw_pile_size() < draw_pile_break or
(state->draw_pile_size() == draw_pile_break and state->last_action_type() == ActionType::clue))
while (next_action > 0 and (state->draw_pile_size() < draw_pile_break or
(state->draw_pile_size() == draw_pile_break and state->last_action_type() == ActionType::clue)))
{
revert_turn();
}

View file

@ -5,24 +5,22 @@ namespace Hanabi
{
std::unique_ptr<Hanabi::HanabiStateIF> make_game_state(
std::size_t num_suits, Hanabi::player_t num_players, std::vector<Hanabi::Card> const & deck,
clue_t num_clues_gained_on_discard_or_stack_finished, std::optional<
uint8_t> score_goal
Hanabi::HanabiStateConfig config
)
{
uint8_t actual_score_goal = score_goal.value_or(5 * num_suits);
switch (num_players)
{
case 2:
switch (num_suits)
{
case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 2, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 2, 5>(deck, config));
case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 2, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 2, 5>(deck, config));
case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 2, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 2, 5>(deck, config));
case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 2, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 2, 5>(deck, config));
default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
}
@ -30,13 +28,13 @@ namespace Hanabi
switch (num_suits)
{
case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 3, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 3, 5>(deck, config));
case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 3, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 3, 5>(deck, config));
case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 3, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 3, 5>(deck, config));
case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 3, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 3, 5>(deck, config));
default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
}
@ -44,13 +42,13 @@ namespace Hanabi
switch (num_suits)
{
case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 4, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 4, 4>(deck, config));
case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 4, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 4, 4>(deck, config));
case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 4, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 4, 4>(deck, config));
case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 4, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 4, 4>(deck, config));
default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
}
@ -58,13 +56,13 @@ namespace Hanabi
switch (num_suits)
{
case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 5, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 5, 4>(deck, config));
case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 5, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 5, 4>(deck, config));
case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 5, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 5, 4>(deck, config));
case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 5, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 5, 4>(deck, config));
default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
}
@ -72,13 +70,13 @@ namespace Hanabi
switch (num_suits)
{
case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 6, 3>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 6, 3>(deck, config));
case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 6, 3>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 6, 3>(deck, config));
case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 6, 3>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 6, 3>(deck, config));
case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 6, 3>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished));
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 6, 3>(deck, config));
default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
}

3
src/seed_search.cpp Normal file
View file

@ -0,0 +1,3 @@
//
// Created by maximilian on 3/12/24.
//

View file

@ -30,9 +30,9 @@ namespace Hanabi
return ret;
}
const static std::array<std::string, 13> cli_commands = {
const static std::array<std::string, 15> cli_commands = {
"play", "clue", "discard", "opt", "state", "id", "revert", "actions", "evaluate", "help", "quit", "set-initials"
, "dump-id-parts",
, "dump-id-parts", "toggle-auto-evaluation", "toggle-reasonable-actions"
};
char *cli_commands_generator(const char *text, int state)
@ -192,6 +192,8 @@ namespace Hanabi
// Tracks the depth of the replay the user explores. We have to ensure that we don't revert too much.
unsigned depth = 0;
bool evaluate_actions_on_query = false;
bool show_only_reasonable_actions = true;
while (true)
{
@ -200,26 +202,28 @@ namespace Hanabi
if (prompt.find("help") == 0)
{
std::cout << "state: print information on current game state." << std::endl;
std::cout << "clue: give a clue." << std::endl;
std::cout << "play <card>: play specified card." << std::endl;
std::cout << "discard: discard trash from hand." << std::endl;
std::cout
<< "opt: take optimal action. In case of ties, prefers plays and discards in that order."
<< std::endl;
std::cout << "revert <turns>: revert specified number of turns (default 1)." << std::endl;
std::cout << "actions: display list of reasonable actions to take and their winning chances."
std::cout << "state: print information on current game state." << std::endl;
std::cout << "clue: give a clue." << std::endl;
std::cout << "play <card>: play specified card." << std::endl;
std::cout << "discard: discard trash from hand." << std::endl;
std::cout << "opt: take optimal action. In case of ties, prefers plays and discards in that order."
<< std::endl;
std::cout << "evaluate: evaluate current game state recursively. Potentially runtime-expensive."
std::cout << "revert <turns>: revert specified number of turns (default 1)." << std::endl;
std::cout << "actions: display list of reasonable actions to take and their winning chances."
<< std::endl;
std::cout << "set-initials <chars>: Set initials for the suits." << std::endl;
std::cout << "(q)uit: Quit this interactive shell." << std::endl;
std::cout << "id: display id of state. Has no inherent meaning, useful for debugging."
std::cout << "evaluate: evaluate current game state recursively. Potentially runtime-expensive."
<< std::endl;
std::cout
<< "dump-id-parts: Dump parts used to calculate the id of the state as well as the cards associated to them."
<< std::endl;
std::cout << "help: Display this help message." << std::endl;
std::cout << "toggle-auto-evaluation: If set to true, all available game actions will be evaluated upon listing them. "
<< "Currently set to " << std::boolalpha << evaluate_actions_on_query << "." << std::endl;
std::cout << "toggle-reasonable-actions: If set to true, only reasonable actions will be suggested. "
<< "Currently set to " << std::boolalpha << show_only_reasonable_actions << "." << std::endl;
std::cout << "set-initials <chars>: Set initials for the suits." << std::endl;
std::cout << "(q)uit: Quit this interactive shell." << std::endl;
std::cout << "id: display id of state. Has no inherent meaning, useful for debugging."
<< std::endl;
std::cout << "dump-id-parts: Dump parts used to calculate the id of the state as well as the cards associated to them."
<< std::endl;
std::cout << "help: Display this help message." << std::endl;
continue;
}
@ -284,6 +288,20 @@ namespace Hanabi
continue;
}
if (prompt.find("toggle-auto-evaluation") == 0)
{
evaluate_actions_on_query = !evaluate_actions_on_query;
std::cout << "Toggled auto-evaluation, now set to " << std::boolalpha << evaluate_actions_on_query << "." << std::endl;
continue;
}
if (prompt.find("toggle-reasonable-actions") == 0)
{
show_only_reasonable_actions = !show_only_reasonable_actions;
std::cout << "Toggled showing only reasonable actions, now set to " << std::boolalpha << show_only_reasonable_actions << "." << std::endl;
continue;
}
if (prompt.find("revert") == 0)
{
if (depth == 0)
@ -397,7 +415,18 @@ namespace Hanabi
if (prompt.find("actions") == 0)
{
auto reasonable_actions = game.state->get_reasonable_actions();
std::vector<std::pair<Hanabi::Action, std::optional<Hanabi::probability_t>>> reasonable_actions = game.state->get_reasonable_actions(evaluate_actions_on_query, show_only_reasonable_actions);
std::sort(reasonable_actions.begin(), reasonable_actions.end(),
[](std::pair<Hanabi::Action, std::optional<probability_t>> const & left,
std::pair<Hanabi::Action, std::optional<probability_t>> const & right){
if (not left.second.has_value()) {
return false;
}
if (not right.second.has_value()) {
return true;
}
return left.second.value() > right.second.value();
});
int max_rational_digit_len = std::accumulate(
reasonable_actions.begin(), reasonable_actions.end(), 0, [](
int old, const std::pair<Action

29
src/version.cpp.in Normal file
View file

@ -0,0 +1,29 @@
/**
* This file is auto-generated on every build by cmake.
* Do not edit this, or git version information will be out of sync.
* If you wish to modify this, edit src/version.cpp.in instead.
*/
#include <version.h>
#include <myassert.h>
#define GIT_SHA1 "@GIT_SHA1@"
#define GIT_REFSPEC "@GIT_RECFSPEC@"
#define GIT_DESCRIPTION "@GIT_DESCRIPTION@"
#define GIT_LOCAL_CHANGES "@GIT_LOCAL_CHANGES@"
namespace Version {
const std::string git_sha1 = GIT_SHA1;
const std::string git_description = GIT_DESCRIPTION;
const std::string git_refspec = GIT_REFSPEC;
const std::string git_local_changes = GIT_LOCAL_CHANGES;
bool is_dirty() {
if (git_local_changes == "DIRTY")
{
return true;
}
ASSERT(git_local_changes == "CLEAN");
return false;
}
} // namespace Version

View file

@ -1,49 +0,0 @@
#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp>
#include <boost/json.hpp>
#include "game_state.h"
#include "parse_game.h"
using namespace Hanabi;
std::string const game_json_str = R"({"id":1058099,"players":["NoVarkusKahlsen","RamaNoVarjan","PurpleJoeVar"],"deck":[{"suitIndex":3,"rank":5},{"suitIndex":2,"rank":2},{"suitIndex":4,"rank":5},{"suitIndex":3,"rank":1},{"suitIndex":1,"rank":3},{"suitIndex":2,"rank":1},{"suitIndex":1,"rank":2},{"suitIndex":4,"rank":2},{"suitIndex":2,"rank":3},{"suitIndex":2,"rank":5},{"suitIndex":4,"rank":1},{"suitIndex":3,"rank":1},{"suitIndex":0,"rank":3},{"suitIndex":4,"rank":1},{"suitIndex":3,"rank":4},{"suitIndex":4,"rank":1},{"suitIndex":0,"rank":4},{"suitIndex":4,"rank":4},{"suitIndex":3,"rank":1},{"suitIndex":0,"rank":3},{"suitIndex":0,"rank":1},{"suitIndex":2,"rank":1},{"suitIndex":2,"rank":1},{"suitIndex":3,"rank":3},{"suitIndex":1,"rank":1},{"suitIndex":3,"rank":2},{"suitIndex":1,"rank":4},{"suitIndex":1,"rank":4},{"suitIndex":3,"rank":2},{"suitIndex":0,"rank":4},{"suitIndex":0,"rank":2},{"suitIndex":0,"rank":1},{"suitIndex":0,"rank":2},{"suitIndex":4,"rank":2},{"suitIndex":2,"rank":4},{"suitIndex":1,"rank":1},{"suitIndex":1,"rank":3},{"suitIndex":3,"rank":4},{"suitIndex":2,"rank":2},{"suitIndex":4,"rank":4},{"suitIndex":2,"rank":4},{"suitIndex":2,"rank":3},{"suitIndex":1,"rank":2},{"suitIndex":1,"rank":1},{"suitIndex":0,"rank":5},{"suitIndex":1,"rank":5},{"suitIndex":0,"rank":1},{"suitIndex":3,"rank":3},{"suitIndex":4,"rank":3},{"suitIndex":4,"rank":3}],"actions":[{"type":2,"target":1,"value":2},{"type":0,"target":5,"value":0},{"type":2,"target":0,"value":2},{"type":2,"target":2,"value":0},{"type":0,"target":15,"value":0},{"type":3,"target":1,"value":2},{"type":2,"target":1,"value":4},{"type":3,"target":0,"value":5},{"type":1,"target":10,"value":0},{"type":0,"target":1,"value":0},{"type":2,"target":2,"value":3},{"type":3,"target":1,"value":3},{"type":1,"target":3,"value":0},{"type":0,"target":7,"value":0},{"type":2,"target":0,"value":3},{"type":3,"target":2,"value":4},{"type":0,"target":20,"value":0},{"type":1,"target":11,"value":0},{"type":0,"target":18,"value":0},{"type":3,"target":2,"value":1},{"type":1,"target":13,"value":0},{"type":0,"target":23,"value":0},{"type":2,"target":2,"value":3},{"type":0,"target":24,"value":0},{"type":0,"target":25,"value":0},{"type":0,"target":8,"value":0},{"type":1,"target":22,"value":0},{"type":0,"target":27,"value":0},{"type":1,"target":21,"value":0},{"type":2,"target":0,"value":0},{"type":0,"target":30,"value":0},{"type":0,"target":6,"value":0},{"type":3,"target":0,"value":3},{"type":0,"target":4,"value":0},{"type":1,"target":28,"value":0},{"type":3,"target":1,"value":5},{"type":0,"target":34,"value":0},{"type":0,"target":9,"value":0},{"type":0,"target":12,"value":0},{"type":2,"target":2,"value":1},{"type":1,"target":31,"value":0},{"type":0,"target":26,"value":0},{"type":1,"target":19,"value":0},{"type":1,"target":33,"value":0},{"type":2,"target":1,"value":0},{"type":1,"target":32,"value":0},{"type":0,"target":16,"value":0},{"type":2,"target":1,"value":0},{"type":3,"target":1,"value":5},{"type":0,"target":44,"value":0},{"type":3,"target":1,"value":5},{"type":1,"target":36,"value":0},{"type":0,"target":45,"value":0},{"type":3,"target":1,"value":3},{"type":3,"target":1,"value":3},{"type":0,"target":47,"value":0},{"type":0,"target":14,"value":0},{"type":0,"target":0,"value":0},{"type":0,"target":48,"value":0},{"type":0,"target":17,"value":0}],"seed":"p3v0s23"})";
boost::json::value const game_json = boost::json::parse(game_json_str);
BOOST_AUTO_TEST_SUITE(parsing)
BOOST_AUTO_TEST_CASE(parse_deck)
{
BOOST_CHECK_NO_THROW(auto deck = Parsing::parse_deck(game_json.at("deck")));
auto const [deck, num_suits] = Parsing::parse_deck(game_json.at("deck"));
BOOST_CHECK_EQUAL(num_suits, 5);
BOOST_REQUIRE_EQUAL(deck.size(), 50);
BOOST_CHECK_EQUAL(deck[0], Cards::b5);
BOOST_CHECK_EQUAL(deck[1], Cards::g2);
BOOST_CHECK_EQUAL(deck[2], Cards::p5);
BOOST_CHECK_EQUAL(deck[3], Cards::b1);
BOOST_CHECK_EQUAL(deck[4], Cards::y3);
BOOST_CHECK_EQUAL(deck[5], Cards::g1);
BOOST_CHECK_EQUAL(deck[6], Cards::y2);
BOOST_CHECK_EQUAL(deck[7], Cards::p2);
BOOST_CHECK_EQUAL(deck[8], Cards::g3);
BOOST_CHECK_EQUAL(deck[9], Cards::g5);
BOOST_CHECK_EQUAL(deck[10], Cards::p1);
BOOST_CHECK_EQUAL(deck[11], Cards::b1);
BOOST_CHECK_EQUAL(deck[12], Cards::r3);
BOOST_CHECK_EQUAL(deck[13], Cards::p1);
BOOST_CHECK_EQUAL(deck[14], Cards::b4);
}
BOOST_AUTO_TEST_CASE(parse_actions)
{
BOOST_CHECK_NO_THROW(auto actions = Parsing::parse_actions(game_json.at("actions")));
std::vector<Parsing::HanabLiveAction> actions = Parsing::parse_actions(game_json.at("actions"));
BOOST_CHECK_EQUAL(actions[0].type, ActionType::color_clue);
}
BOOST_AUTO_TEST_CASE(parse_game)
{
BOOST_CHECK_NO_THROW(auto game = Parsing::parse_game(game_json.as_object()));
}
BOOST_AUTO_TEST_SUITE_END()