diff --git a/Src/cata_utility.h b/Src/cata_utility.h new file mode 100644 index 0000000000..ef7e2b94c4 --- /dev/null +++ b/Src/cata_utility.h @@ -0,0 +1,568 @@ +#pragma once +#ifndef CATA_SRC_CATA_UTILITY_H +#define CATA_SRC_CATA_UTILITY_H + +#include +#include +#include +#include +#include +#include +#include + +#include "units.h" + +class JsonIn; +class JsonOut; +class translation; + +/** + * Greater-than comparison operator; required by the sort interface + */ +struct pair_greater_cmp_first { + template< class T, class U > + bool operator()( const std::pair &a, const std::pair &b ) const { + return a.first > b.first; + } + +}; + +/** + * For use with smart pointers when you don't actually want the deleter to do + * anything. + */ +struct null_deleter { + template + void operator()( T * ) const {} +}; + +/** + * Type of object that a measurement is taken on. Used, for example, to display wind speed in m/s + * while displaying vehicle speed in km/h. + */ +enum units_type { + VU_VEHICLE, + VU_WIND +}; + +/** + * Round a floating point value down to the nearest integer + * + * Optimized floor function, similar to std::floor but faster. + */ +inline int fast_floor( double v ) +{ + return static_cast( v ) - ( v < static_cast( v ) ); +} + +/** + * Round a value up at a given decimal place. + * + * @param val Value to be rounded. + * @param dp Decimal place to round the value at. + * @return Rounded value. + */ +double round_up( double val, unsigned int dp ); + +/** Divide @p num by @p den, rounding up +* +* @p num must be non-negative, @p den must be positive, and @c num+den must not overflow. +*/ +template::value, int>::type = 0> +T divide_round_up( T num, T den ) +{ + return ( num + den - 1 ) / den; +} + +/** Divide @p num by @p den, rounding up + * + * @p num must be non-negative, @p den must be positive, and @c num+den must not overflow. + */ +template +T divide_round_up( units::quantity num, units::quantity den ) +{ + return divide_round_up( num.value(), den.value() ); +} + +int divide_round_down( int a, int b ); + +/** + * Determine whether a value is between two given boundaries. + * + * @param test Value to be tested. + * @param down Lower boundary for value. + * @param up Upper boundary for value. + * + * @return True if test value is greater than lower boundary and less than upper + * boundary, otherwise returns false. + */ +bool isBetween( int test, int down, int up ); + +/** + * Perform case sensitive search for a query string inside a subject string. + * + * Searches for string given by qry inside a subject string given by str. + * + * @param str Subject to search for occurrence of the query string. + * @param qry Query string to search for in str + * + * @return true if the query string is found at least once within the subject + * string, otherwise returns false + */ +bool lcmatch( const std::string &str, const std::string &qry ); +bool lcmatch( const translation &str, const std::string &qry ); + +/** + * Matches text case insensitive with the include/exclude rules of the filter + * + * Multiple includes/excludes are possible + * + * Examle: bank,-house,tank,-car + * Will match text containing tank or bank while not containing house or car + * + * @param text String to be matched + * @param filter String with include/exclude rules + * + * @return true if include/exclude rules pass. See Example. + */ +bool match_include_exclude( const std::string &text, std::string filter ); + +/** + * Basic logistic function. + * + * Calculates the value at a single point on a standard logistic curve. + * + * @param t Point on logistic curve to retrieve value for + * + * @return Value of the logistic curve at the given point + */ +double logarithmic( double t ); + +/** + * Normalized logistic function + * + * Generates a logistic curve on the domain [-6,6], then normalizes such that + * the value ranges from 1 to 0. A single point is then calculated on this curve. + * + * @param min t-value that should yield an output of 1 on the scaled curve. + * @param max t-value that should yield an output of 0 on the scaled curve. + * @param pos t-value to calculate the output for. + * + * @return The value of the scaled logistic curve at point pos. + */ +double logarithmic_range( int min, int max, int pos ); + +/** + * Clamp the value of a modifier in order to bound the resulting value + * + * Ensures that a modifier value will not cause a base value to exceed given + * bounds when applied. If necessary, the given modifier value is increased or + * reduced to meet this constraint. + * + * Giving a value of zero for min or max indicates that there is no minimum or + * maximum boundary, respectively. + * + * @param val The base value that the modifier will be applied to + * @param mod The desired modifier to be added to the base value + * @param max The desired maximum value of the base value after modification, or zero. + * @param min The desired minimum value of the base value after modification, or zero. + * + * @returns Value of mod, possibly altered to respect the min and max boundaries + */ +int bound_mod_to_vals( int val, int mod, int max, int min ); + +/** + * Create a units label for a velocity value. + * + * Gives the name of the velocity unit in the user selected unit system, either + * "km/h", "ms/s" or "mph". Used to add abbreviated unit labels to the output of + * @ref convert_velocity. + * + * @param vel_units type of velocity desired (i.e. wind or vehicle) + * + * @return name of unit. + */ +const char *velocity_units( units_type vel_units ); + +/** + * Create a units label for a weight value. + * + * Gives the name of the weight unit in the user selected unit system, either + * "kgs" or "lbs". Used to add unit labels to the output of @ref convert_weight. + * + * @return name of unit + */ +const char *weight_units(); + +/** + * Create an abbreviated units label for a volume value. + * + * Returns the abbreviated name for the volume unit for the user selected unit system, + * i.e. "c", "L", or "qt". Used to add unit labels to the output of @ref convert_volume. + * + * @return name of unit. + */ +const char *volume_units_abbr(); + +/** + * Create a units label for a volume value. + * + * Returns the abbreviated name for the volume unit for the user selected unit system, + * ie "cup", "liter", or "quart". Used to add unit labels to the output of @ref convert_volume. + * + * @return name of unit. + */ +const char *volume_units_long(); + +/** + * Convert internal velocity units to units defined by user. + * + * @param velocity A velocity value in internal units. + * @param vel_units General type of item this velocity is for (e.g. vehicles or wind) + * + * @returns Velocity in the user selected measurement system and in appropriate + * units for the object being measured. + */ +double convert_velocity( int velocity, units_type vel_units ); + +/** + * Convert weight in grams to units defined by user (kg or lbs) + * + * @param weight to be converted. + * + * @returns Weight converted to user selected unit + */ +double convert_weight( const units::mass &weight ); + +/** + * converts length to largest unit available + * 1000 mm = 1 meter for example + * assumed to be used in conjunction with unit string functions + * also works for imperial units + */ +int convert_length( const units::length &length ); +std::string length_units( const units::length &length ); + +/** convert a mass unit to a string readable by a human */ +std::string weight_to_string( const units::mass &weight ); + +/** + * Convert volume from ml to units defined by user. + */ +double convert_volume( int volume ); + +/** + * Convert volume from ml to units defined by user, + * optionally returning the units preferred scale. + */ +double convert_volume( int volume, int *out_scale ); + +/** convert a volume unit to a string readable by a human */ +std::string vol_to_string( const units::volume &vol ); + +/** + * Convert a temperature from degrees Fahrenheit to degrees Celsius. + * + * @return Temperature in degrees C. + */ +double temp_to_celsius( double fahrenheit ); + +/** + * Convert a temperature from degrees Fahrenheit to Kelvin. + * + * @return Temperature in degrees K. + */ +double temp_to_kelvin( double fahrenheit ); + +/** + * Convert a temperature from Kelvin to degrees Fahrenheit. + * + * @return Temperature in degrees C. + */ +double kelvin_to_fahrenheit( double kelvin ); + +/** + * Clamp (number and space wise) value to with, + * taking into account the specified preferred scale, + * returning the adjusted (shortened) scale that best fit the width, + * optionally returning a flag that indicate if the value was truncated to fit the width + */ +/**@{*/ +double clamp_to_width( double value, int width, int &scale ); +double clamp_to_width( double value, int width, int &scale, bool *out_truncated ); +/**@}*/ + +/** + * Clamp first argument so that it is no lower than second and no higher than third. + * Does not check if min is lower than max. + */ +template +constexpr T clamp( const T &val, const T &min, const T &max ) +{ + return std::max( min, std::min( max, val ) ); +} + +/** + * Linear interpolation: returns first argument if t is 0, second if t is 1, otherwise proportional to t. + * Does not clamp t, meaning it can return values lower than min (if t<0) or higher than max (if t>1). + */ +template +constexpr T lerp( const T &min, const T &max, float t ) +{ + return ( 1.0f - t ) * min + t * max; +} + +/** Linear interpolation with t clamped to [0, 1] */ +template +constexpr T lerp_clamped( const T &min, const T &max, float t ) +{ + return lerp( min, max, clamp( t, 0.0f, 1.0f ) ); +} + +/** + * From `points`, finds p1 and p2 such that p1.first < x < p2.first + * Then linearly interpolates between p1.second and p2.second and returns the result. + * `points` should be sorted by first elements of the pairs. + * If x is outside range, returns second value of the first (if x < points[0].first) or last point. + */ +float multi_lerp( const std::vector> &points, float x ); + +/** + * @brief Class used to access a list as if it were circular. + * + * Some times you just want to have a list loop around on itself. + * This wrapper class allows you to do that. It requires the list to exist + * separately, but that also means any changes to the list get propagated (both ways). + */ +template +class list_circularizer +{ + private: + unsigned int _index = 0; + std::vector *_list; + public: + /** Construct list_circularizer from an existing std::vector. */ + list_circularizer( std::vector &_list ) : _list( &_list ) { + } + + /** Advance list to next item, wrapping back to 0 at end of list */ + void next() { + _index = ( _index == _list->size() - 1 ? 0 : _index + 1 ); + } + + /** Advance list to previous item, wrapping back to end at zero */ + void prev() { + _index = ( _index == 0 ? _list->size() - 1 : _index - 1 ); + } + + /** Return list element at the current location */ + T &cur() const { + // list could be null, but it would be a design time mistake and really, the callers fault. + return ( *_list )[_index]; + } +}; + +/** + * Open a file for writing, calls the writer on that stream. + * + * If the writer throws, or if the file could not be opened or if any I/O error + * happens, the function shows a popup containing the + * \p fail_message, the error text and the path. + * + * @return Whether saving succeeded (no error was caught). + * @throw The void function throws when writing failes or when the @p writer throws. + * The other function catches all exceptions and returns false. + */ +///@{ +bool write_to_file( const std::string &path, const std::function &writer, + const char *fail_message ); +void write_to_file( const std::string &path, const std::function &writer ); +///@} + +class JsonDeserializer; + +/** + * Try to open and read from given file using the given callback. + * + * The file is opened for reading (binary mode), given to the callback (which does the actual + * reading) and closed. + * Any exceptions from the callbacks are caught and reported as `debugmsg`. + * If the stream is in a fail state (other than EOF) after the callback returns, it is handled as + * error as well. + * + * The callback can either be a generic `std::istream`, a @ref JsonIn stream (which has been + * initialized from the `std::istream`) or a @ref JsonDeserializer object (in case of the later, + * it's `JsonDeserializer::deserialize` method will be invoked). + * + * The functions with the "_optional" prefix do not show a debug message when the file does not + * exist. They simply ignore the call and return `false` immediately (without calling the callback). + * They can be used for loading legacy files. + * + * @return `true` is the file was read without any errors, `false` upon any error. + */ +/**@{*/ +bool read_from_file( const std::string &path, const std::function &reader ); +bool read_from_file_json( const std::string &path, const std::function &reader ); +bool read_from_file( const std::string &path, JsonDeserializer &reader ); + +bool read_from_file_optional( const std::string &path, + const std::function &reader ); +bool read_from_file_optional_json( const std::string &path, + const std::function &reader ); +bool read_from_file_optional( const std::string &path, JsonDeserializer &reader ); +/**@}*/ +/** + * Wrapper around std::ofstream that handles error checking and throws on errors. + * + * Use like a normal ofstream: the stream is opened in the constructor and + * closed via @ref close. Both functions check for success and throw std::exception + * upon any error (e.g. when opening failed or when the stream is in an error state when + * being closed). + * Use @ref stream (or the implicit conversion) to access the output stream and to write + * to it. + * + * @note: The stream is closed in the destructor, but no exception is throw from it. To + * ensure all errors get reported correctly, you should always call `close` explicitly. + * + * @note: This uses exclusive I/O. + */ +class ofstream_wrapper +{ + private: + std::ofstream file_stream; + std::string path; + std::string temp_path; + + void open( std::ios::openmode mode ); + + public: + ofstream_wrapper( const std::string &path, std::ios::openmode mode ); + ~ofstream_wrapper(); + + std::ostream &stream() { + return file_stream; + } + operator std::ostream &() { + return file_stream; + } + + void close(); +}; + +std::istream &safe_getline( std::istream &ins, std::string &str ); + +/** Apply fuzzy effect to a string like: + * Hello, world! --> H##lo, wurl#! + * + * @param str the original string to be processed + * @param f the function that guides how to mess the message + * f() will be called for each character (lingual, not byte): + * [-] f() == -1 : nothing will be done + * [-] f() == 0 : the said character will be replace by a random character + * [-] f() == ch : the said character will be replace by ch + * + * @return The processed string + * + */ + +std::string obscure_message( const std::string &str, const std::function &f ); + +/** + * @group JSON (de)serialization wrappers. + * + * The functions here provide a way to (de)serialize objects without actually + * including "json.h". The `*_wrapper` function create the JSON stream instances + * and therefor require "json.h", but the caller doesn't. Callers should just + * forward the stream reference to the actual (de)serialization function. + * + * The inline function do this by calling `T::(de)serialize` (which is assumed + * to exist with the proper signature). + * + * @throws std::exception Deserialization functions may throw upon malformed + * JSON or unexpected/invalid content. + */ +/**@{*/ +std::string serialize_wrapper( const std::function &callback ); +void deserialize_wrapper( const std::function &callback, + const std::string &data ); + +template +inline std::string serialize( const T &obj ) +{ + return serialize_wrapper( [&obj]( JsonOut & jsout ) { + obj.serialize( jsout ); + } ); +} + +template +inline void deserialize( T &obj, const std::string &data ) +{ + deserialize_wrapper( [&obj]( JsonIn & jsin ) { + obj.deserialize( jsin ); + }, data ); +} +/**@}*/ + +/** + * \brief Returns true iff s1 starts with s2 + */ +bool string_starts_with( const std::string &s1, const std::string &s2 ); + +/** + * \brief Returns true iff s1 ends with s2 + */ +bool string_ends_with( const std::string &s1, const std::string &s2 ); + +/** Used as a default filter in various functions */ +template +bool return_true( const T & ) +{ + return true; +} + +/** + * Joins a vector of `std::string`s into a single string with a delimiter/joiner + */ +std::string join( const std::vector &strings, const std::string &joiner ); + +int modulo( int v, int m ); + +class on_out_of_scope +{ + private: + std::function func; + public: + on_out_of_scope( const std::function &func ) : func( func ) { + } + + ~on_out_of_scope() { + if( func ) { + func(); + } + } + + void cancel() { + func = nullptr; + } +}; + +template +class restore_on_out_of_scope +{ + private: + T &t; + T orig_t; + on_out_of_scope impl; + public: + // *INDENT-OFF* + restore_on_out_of_scope( T &t_in ) : t( t_in ), orig_t( t_in ), + impl( [this]() { t = std::move( orig_t ); } ) { + } + + restore_on_out_of_scope( T &&t_in ) : t( t_in ), orig_t( std::move( t_in ) ), + impl( [this]() { t = std::move( orig_t ); } ) { + } + // *INDENT-ON* +}; + +#endif // CATA_SRC_CATA_UTILITY_H diff --git a/Src/chkjson/CMakeLists.txt b/Src/chkjson/CMakeLists.txt new file mode 100644 index 0000000000..7a54b93c3e --- /dev/null +++ b/Src/chkjson/CMakeLists.txt @@ -0,0 +1,33 @@ +# JSON check utility +cmake_minimum_required(VERSION 2.8.12) + +SET (CHKJSON_SOURCES + ${CMAKE_SOURCE_DIR}/src/json.cpp + ${CMAKE_SOURCE_DIR}/src/chkjson/chkjson.cpp +) + +SET (CHKJSON_HEADERS + ${CMAKE_SOURCE_DIR}/src/json.h +) + +# test chain +add_custom_target ( + check_json + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} +) + +add_custom_command ( + TARGET check_json + PRE_BUILD + COMMAND chkjson + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} +) + +INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/chkjson ) + +# Add the actual executable +IF(WIN32) + ADD_EXECUTABLE(chkjson WIN32 EXCLUDE_FROM_ALL ${CHKJSON_SOURCES} ${CHKJSON_HEADERS}) +ELSE(WIN32) + ADD_EXECUTABLE(chkjson EXCLUDE_FROM_ALL ${CHKJSON_SOURCES} ${CHKJSON_HEADERS}) +ENDIF(WIN32) diff --git a/Src/chkjson/chkjson.cpp b/Src/chkjson/chkjson.cpp new file mode 100644 index 0000000000..46d08471de --- /dev/null +++ b/Src/chkjson/chkjson.cpp @@ -0,0 +1,173 @@ +/* Main Loop for cataclysm + * Linux only I guess + * But maybe not + * Who knows + */ +#include +#include +#include +#include +#include // for strcmp +#include // for stack (obviously) +#include +#include +#include +#include // for throwing errors +#include +#include +#include + +#include "json.h" + +// copypasta: file_finder.cpp +static std::vector get_files_from_path( std::string extension, std::string root_path, + bool recursive_search, bool match_extension ) +{ + std::vector files; + const size_t extsz = extension.size(); + const char *c_extension = extension.c_str(); + // test for empty root path + if( root_path.empty() ) { + root_path = "."; + } + + std::stack directories, tempstack; + directories.push( root_path ); + std::string path; + + while( !directories.empty() ) { + path = directories.top(); + directories.pop(); + + DIR *root = opendir( path.c_str() ); + + if( root ) { + struct dirent *root_file; + struct stat _buff; + DIR *subdir; + + while( ( root_file = readdir( root ) ) ) { + // check to see if it is a folder! + if( stat( root_file->d_name, &_buff ) != 0x4 ) { + // ignore '.' and '..' folder names, which are current and parent folder relative paths + if( ( strcmp( root_file->d_name, "." ) != 0 ) && ( strcmp( root_file->d_name, ".." ) != 0 ) ) { + std::string subpath = path + "/" + root_file->d_name; + + if( recursive_search ) { + subdir = opendir( subpath.c_str() ); + if( subdir ) { + tempstack.push( subpath ); + closedir( subdir ); + } + } + } + } + // check to see if it is a file with the appropriate extension + std::string tmp = root_file->d_name; + if( tmp.find( c_extension, match_extension ? tmp.size() - extsz : 0 ) != std::string::npos ) { + std::string fullpath = path + "/" + tmp; + files.push_back( fullpath ); + } + } + closedir( root ); + } + // Directories are added to tempstack in A->Z order, which makes them pop from Z->A. This Makes sure that directories are + // searched in the proper order and that the final output is in the proper order. + while( !tempstack.empty() ) { + directories.push( tempstack.top() ); + tempstack.pop(); + } + } + return files; +} + +// copypasta: init.cpp +static void load_object( JsonObject &jo ) +{ + std::string type = jo.get_string( "type" ); + if( !jo.has_string( "type" ) ) { + jo.throw_error( "JSON object has no type" ); + } +} +static void load_all_from_json( JsonIn &jsin ) +{ + char ch; + jsin.eat_whitespace(); + // examine first non-whitespace char + ch = jsin.peek(); + if( ch == '{' ) { + // find type and dispatch single object + JsonObject jo = jsin.get_object(); + load_object( jo ); + jo.finish(); + // if there's anything else in the file, it's an error. + jsin.eat_whitespace(); + if( jsin.good() ) { + std::stringstream err; + err << "expected single-object file but found '"; + err << jsin.peek() << "'"; + jsin.error( err.str() ); + } + } else if( ch == '[' ) { + jsin.start_array(); + // find type and dispatch each object until array close + while( !jsin.end_array() ) { + jsin.eat_whitespace(); + ch = jsin.peek(); + if( ch != '{' ) { + std::stringstream err; + err << "expected array of objects but found '"; + err << ch << "', not '{'"; + jsin.error( err.str() ); + } + JsonObject jo = jsin.get_object(); + load_object( jo ); + jo.finish(); + } + } else { + // not an object or an array? + std::stringstream err; + err << "expected object or array, but found '" << ch << "'"; + jsin.error( err.str() ); + } +} + +static void load_json_dir( const std::string &dirname ) +{ + // get a list of all files in the directory + std::vector dir = + get_files_from_path( ".json", dirname, true, true ); + // iterate over each file + std::vector::iterator it; + for( it = dir.begin(); it != dir.end(); it++ ) { + // open the file as a stream + std::ifstream infile( it->c_str(), std::ifstream::in | std::ifstream::binary ); + // and stuff it into ram + std::istringstream iss( + std::string( + ( std::istreambuf_iterator( infile ) ), + std::istreambuf_iterator() + ) + ); + infile.close(); + // parse it + try { + JsonIn jsin( iss ); + load_all_from_json( jsin ); + } catch( const JsonError &err ) { + throw std::runtime_error( *( it ) + ": " + err.what() ); + } + } +} + +int main( int, char ** ) +{ + setlocale( LC_ALL, "" ); + try { + load_json_dir( "data/json" ); + } catch( const std::exception &err ) { + printf( "Error: %s\n", err.what() ); + return 1; + } + return 0; +} diff --git a/Src/colony.h b/Src/colony.h new file mode 100644 index 0000000000..f335258958 --- /dev/null +++ b/Src/colony.h @@ -0,0 +1,3517 @@ +// This software is a modified version of the original. +// The original license is as follows: + +// Copyright (c) 2019, Matthew Bentley (mattreecebentley@gmail.com) www.plflib.org + +// zLib license (https://www.zlib.net/zlib_license.html): +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgement in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +#pragma once +#ifndef CATA_SRC_COLONY_H +#define CATA_SRC_COLONY_H + +// Compiler-specific defines used by colony: +#define COLONY_CONSTEXPR +#define COLONY_NOEXCEPT_MOVE_ASSIGNMENT(the_allocator) noexcept +#define COLONY_NOEXCEPT_SWAP(the_allocator) noexcept + +// TODO: Switch to these when we move to C++17 +// #define COLONY_CONSTEXPR constexpr +// #define COLONY_NOEXCEPT_MOVE_ASSIGNMENT(the_allocator) noexcept(std::allocator_traits::is_always_equal::value) +// #define COLONY_NOEXCEPT_SWAP(the_allocator) noexcept(std::allocator_traits::propagate_on_container_swap::value) + +// Note: GCC creates faster code without forcing inline +#if defined(_MSC_VER) +#define COLONY_FORCE_INLINE __forceinline +#else +#define COLONY_FORCE_INLINE +#endif + +/* whole GCC 6 family */ +#if __GNUC__ == 6 +/* GCC 6.5 at least complains about type punning, but nothing else does. */ +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + +// TODO: get rid of these defines +#define COLONY_CONSTRUCT(the_allocator, allocator_instance, location, ...) std::allocator_traits::construct(allocator_instance, location, __VA_ARGS__) +#define COLONY_DESTROY(the_allocator, allocator_instance, location) std::allocator_traits::destroy(allocator_instance, location) +#define COLONY_ALLOCATE(the_allocator, allocator_instance, size, hint) std::allocator_traits::allocate(allocator_instance, size, hint) +#define COLONY_ALLOCATE_INITIALIZATION(the_allocator, size, hint) std::allocator_traits::allocate(*this, size, hint) +#define COLONY_DEALLOCATE(the_allocator, allocator_instance, location, size) std::allocator_traits::deallocate(allocator_instance, location, size) + +#include // std::sort and std::fill_n +#include // assert +#include // offsetof, used in blank() +#include // memset, memcpy +#include +#include // std::bidirectional_iterator_tag +#include // std::numeric_limits +#include // std::allocator +#include // std::is_trivially_destructible, etc +#include // std::move + +namespace cata +{ + +template , typename element_skipfield_type = unsigned short > +// Empty base class optimization - inheriting allocator functions +class colony : private element_allocator_type +// Note: unsigned short is equivalent to uint_least16_t i.e. Using 16-bit unsigned integer in best-case scenario, greater-than-16-bit unsigned integer where platform doesn't support 16-bit types +{ + public: + // Standard container typedefs: + using value_type = element_type; + using allocator_type = element_allocator_type; + using skipfield_type = element_skipfield_type; + + using aligned_element_type = typename std::aligned_storage < sizeof( element_type ), + ( alignof( element_type ) > ( sizeof( element_skipfield_type ) * 2 ) ) ? alignof( element_type ) : + ( sizeof( element_skipfield_type ) * 2 ) >::type; + + using size_type = typename std::allocator_traits::size_type; + using difference_type = typename std::allocator_traits::difference_type; + using reference = element_type&; + using const_reference = const element_type&; + using pointer = typename std::allocator_traits::pointer; + using const_pointer = typename std::allocator_traits::const_pointer; + + // Iterator declarations: + template class colony_iterator; + using iterator = colony_iterator; + using const_iterator = colony_iterator; + friend class colony_iterator; // Using above typedef name here is illegal under C++03 + friend class colony_iterator; + + template class colony_reverse_iterator; + using reverse_iterator = colony_reverse_iterator; + using const_reverse_iterator = colony_reverse_iterator; + friend class colony_reverse_iterator; + friend class colony_reverse_iterator; + + private: + + struct group; // forward declaration for typedefs below + + using aligned_element_allocator_type = typename + std::allocator_traits::template rebind_alloc; + using group_allocator_type = typename std::allocator_traits::template + rebind_alloc; + using skipfield_allocator_type = typename std::allocator_traits::template + rebind_alloc; + + // Using uchar as the generic allocator type, as sizeof is always guaranteed to be 1 byte regardless of the number of bits in a byte on given computer, whereas for example, uint8_t would fail on machines where there are more than 8 bits in a byte eg. Texas Instruments C54x DSPs. + using uchar_allocator_type = typename std::allocator_traits::template + rebind_alloc; + // Different typedef to 'pointer' - this is a pointer to the over aligned element type, not the original element type + using aligned_pointer_type = typename + std::allocator_traits::pointer; + using group_pointer_type = typename std::allocator_traits::pointer; + using skipfield_pointer_type = typename std::allocator_traits::pointer; + using uchar_pointer_type = typename std::allocator_traits::pointer; + + using pointer_allocator_type = typename std::allocator_traits::template + rebind_alloc; + + // Colony groups: + + // Empty base class optimization (EBCO) - inheriting allocator functions + struct group : private uchar_allocator_type { + aligned_pointer_type + last_endpoint; // The address that is one past the highest cell number that's been used so far in this group - does not change with erase command but may change with insert (if no previously-erased locations are available) - is necessary because an iterator cannot access the colony's end_iterator. Most-used variable in colony use (operator ++, --) so first in struct + group_pointer_type + next_group; // Next group in the intrusive list of all groups. NULL if no next group + const aligned_pointer_type elements; // Element storage + const skipfield_pointer_type + skipfield; // Skipfield storage. The element and skipfield arrays are allocated contiguously, hence the skipfield pointer also functions as a 'one-past-end' pointer for the elements array. There will always be one additional skipfield node allocated compared to the number of elements. This is to ensure a faster ++ iterator operation (fewer checks are required when this is present). The extra node is unused and always zero, but checked, and not having it will result in out-of-bounds memory errors. + group_pointer_type + previous_group; // previous group in the intrusive list of all groups. NULL if no preceding group + skipfield_type + free_list_head; // The index of the last erased element in the group. The last erased element will, in turn, contain the number of the index of the next erased element, and so on. If this is == maximum skipfield_type value then free_list is empty i.e. no erasures have occurred in the group (or if they have, the erased locations have then been reused via insert()). + const skipfield_type + capacity; // The element capacity of this particular group + skipfield_type + number_of_elements; // indicates total number of active elements in group - changes with insert and erase commands - used to check for empty group in erase function, as an indication to remove the group + group_pointer_type + erasures_list_next_group; // The next group in the intrusive singly-linked list of groups with erasures i.e. with active erased-element free lists + size_type + group_number; // Used for comparison (> < >= <=) iterator operators (used by distance function and user) + + group( const skipfield_type elements_per_group, group_pointer_type const previous = nullptr ): + last_endpoint( reinterpret_cast( COLONY_ALLOCATE_INITIALIZATION( + uchar_allocator_type, ( ( elements_per_group * ( sizeof( aligned_element_type ) ) ) + ( ( + elements_per_group + 1u ) * sizeof( skipfield_type ) ) ), + ( previous == nullptr ) ? nullptr : + previous->elements ) ) ), /* allocating to here purely because it is first in the struct sequence - actual pointer is elements, last_endpoint is only initialized to element's base value initially, then incremented by one below */ + next_group( nullptr ), + elements( last_endpoint++ ), + skipfield( reinterpret_cast( elements + elements_per_group ) ), + previous_group( previous ), + free_list_head( std::numeric_limits::max() ), + capacity( elements_per_group ), + number_of_elements( 1 ), + erasures_list_next_group( nullptr ), + group_number( ( previous == nullptr ) ? 0 : previous->group_number + 1u ) { + // Static casts to unsigned int from short not necessary as C++ automatically promotes lesser types for arithmetic purposes. + std::memset( &*skipfield, 0, sizeof( skipfield_type ) * ( elements_per_group + + 1u ) ); // &* to avoid problems with non-trivial pointers + } + + ~group() noexcept { + // Null check not necessary (for copied group as above) as delete will also perform a null check. + COLONY_DEALLOCATE( uchar_allocator_type, ( *this ), + reinterpret_cast( elements ), + ( capacity * sizeof( aligned_element_type ) ) + ( ( capacity + 1u ) * sizeof( skipfield_type ) ) ); + } + }; + + // Implement const/non-const iterator switching pattern: + template struct choose; + + template struct choose { + using type = is_true; + }; + + template struct choose { + using type = is_false; + }; + + public: + + // Iterators: + template class colony_iterator + { + private: + group_pointer_type group_pointer; + aligned_pointer_type element_pointer; + skipfield_pointer_type skipfield_pointer; + + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename colony::value_type; + using difference_type = typename colony::difference_type; + using pointer = typename + choose::type; + using reference = typename + choose::type; + + friend class colony; + friend class colony_reverse_iterator; + friend class colony_reverse_iterator; + + inline colony_iterator &operator=( const colony_iterator &source ) noexcept { + group_pointer = source.group_pointer; + element_pointer = source.element_pointer; + skipfield_pointer = source.skipfield_pointer; + return *this; + } + + inline colony_iterator &operator=( const colony_iterator < !is_const > &source ) noexcept { + group_pointer = source.group_pointer; + element_pointer = source.element_pointer; + skipfield_pointer = source.skipfield_pointer; + return *this; + } + + // Move assignment - only really necessary if the allocator uses non-standard i.e. smart pointers + inline colony_iterator &operator=( colony_iterator &&source ) + noexcept { // Move is a copy in this scenario + assert( &source != this ); + group_pointer = std::move( source.group_pointer ); + element_pointer = std::move( source.element_pointer ); + skipfield_pointer = std::move( source.skipfield_pointer ); + return *this; + } + + inline colony_iterator &operator=( colony_iterator < !is_const > &&source ) noexcept { + assert( &source != this ); + group_pointer = std::move( source.group_pointer ); + element_pointer = std::move( source.element_pointer ); + skipfield_pointer = std::move( source.skipfield_pointer ); + return *this; + } + + inline COLONY_FORCE_INLINE bool operator==( const colony_iterator &rh ) const noexcept { + return ( element_pointer == rh.element_pointer ); + } + + inline COLONY_FORCE_INLINE bool operator==( const colony_iterator < !is_const > &rh ) const + noexcept { + return ( element_pointer == rh.element_pointer ); + } + + inline COLONY_FORCE_INLINE bool operator!=( const colony_iterator &rh ) const noexcept { + return ( element_pointer != rh.element_pointer ); + } + + inline COLONY_FORCE_INLINE bool operator!=( const colony_iterator < !is_const > &rh ) const + noexcept { + return ( element_pointer != rh.element_pointer ); + } + + // may cause exception with uninitialized iterator + inline COLONY_FORCE_INLINE reference operator*() const { + return *( reinterpret_cast( element_pointer ) ); + } + + inline COLONY_FORCE_INLINE pointer operator->() const noexcept { + return reinterpret_cast( element_pointer ); + } + + colony_iterator &operator++() { + // covers uninitialized colony_iterator + assert( group_pointer != nullptr ); + // Assert that iterator is not already at end() + assert( !( element_pointer == group_pointer->last_endpoint && + group_pointer->next_group != nullptr ) ); + + skipfield_type skip = *( ++skipfield_pointer ); + + // ie. beyond end of available data + if( ( element_pointer += skip + 1 ) == group_pointer->last_endpoint && + group_pointer->next_group != nullptr ) { + group_pointer = group_pointer->next_group; + skip = *( group_pointer->skipfield ); + element_pointer = group_pointer->elements + skip; + skipfield_pointer = group_pointer->skipfield; + } + + skipfield_pointer += skip; + return *this; + } + + inline colony_iterator operator++( int ) { + const colony_iterator copy( *this ); + ++*this; + return copy; + } + + private: + inline COLONY_FORCE_INLINE void check_for_end_of_group_and_progress() { // used by erase + if( element_pointer == group_pointer->last_endpoint && group_pointer->next_group != nullptr ) { + group_pointer = group_pointer->next_group; + skipfield_pointer = group_pointer->skipfield; + element_pointer = group_pointer->elements + *skipfield_pointer; + skipfield_pointer += *skipfield_pointer; + } + } + + public: + + colony_iterator &operator--() { + assert( group_pointer != nullptr ); + assert( !( element_pointer == group_pointer->elements && + group_pointer->previous_group == + nullptr ) ); // Assert that we are not already at begin() - this is not required to be tested in the code below as we don't need a special condition to progress to begin(), like we do with end() in operator ++ + + if( element_pointer != group_pointer->elements ) { // i.e. not already at beginning of group + const skipfield_type skip = *( --skipfield_pointer ); + skipfield_pointer -= skip; + + if( ( element_pointer -= skip + 1 ) != group_pointer->elements - + 1 ) { // i.e. iterator was not already at beginning of colony (with some previous consecutive deleted elements), and skipfield does not takes us into the previous group) + return *this; + } + } + + group_pointer = group_pointer->previous_group; + skipfield_pointer = group_pointer->skipfield + group_pointer->capacity - 1; + element_pointer = ( reinterpret_cast( group_pointer->skipfield ) - 1 ) + - *skipfield_pointer; + skipfield_pointer -= *skipfield_pointer; + + return *this; + } + + inline colony_iterator operator--( int ) { + const colony_iterator copy( *this ); + --*this; + return copy; + } + + inline bool operator>( const colony_iterator &rh ) const noexcept { + return ( ( group_pointer == rh.group_pointer ) & ( element_pointer > rh.element_pointer ) ) || + ( group_pointer != rh.group_pointer && + group_pointer->group_number > rh.group_pointer->group_number ); + } + + inline bool operator<( const colony_iterator &rh ) const noexcept { + return rh > *this; + } + + inline bool operator>=( const colony_iterator &rh ) const noexcept { + return !( rh > *this ); + } + + inline bool operator<=( const colony_iterator &rh ) const noexcept { + return !( *this > rh ); + } + + inline bool operator>( const colony_iterator < !is_const > &rh ) const noexcept { + return ( ( group_pointer == rh.group_pointer ) & ( element_pointer > rh.element_pointer ) ) || + ( group_pointer != rh.group_pointer && + group_pointer->group_number > rh.group_pointer->group_number ); + } + + inline bool operator<( const colony_iterator < !is_const > &rh ) const noexcept { + return rh > *this; + } + + inline bool operator>=( const colony_iterator < !is_const > &rh ) const noexcept { + return !( rh > *this ); + } + + inline bool operator<=( const colony_iterator < !is_const > &rh ) const noexcept { + return !( *this > rh ); + } + + colony_iterator() noexcept: group_pointer( nullptr ), element_pointer( nullptr ), + skipfield_pointer( nullptr ) {} + + private: + // Used by cend(), erase() etc: + colony_iterator( const group_pointer_type group_p, const aligned_pointer_type element_p, + const skipfield_pointer_type skipfield_p ) noexcept: group_pointer( group_p ), + element_pointer( element_p ), skipfield_pointer( skipfield_p ) {} + + public: + + inline colony_iterator( const colony_iterator &source ) noexcept: + group_pointer( source.group_pointer ), + element_pointer( source.element_pointer ), + skipfield_pointer( source.skipfield_pointer ) {} + + inline colony_iterator( const colony_iterator < !is_const > &source ) noexcept: + group_pointer( source.group_pointer ), + element_pointer( source.element_pointer ), + skipfield_pointer( source.skipfield_pointer ) {} + + // move constructor + inline colony_iterator( colony_iterator &&source ) noexcept: + group_pointer( std::move( source.group_pointer ) ), + element_pointer( std::move( source.element_pointer ) ), + skipfield_pointer( std::move( source.skipfield_pointer ) ) {} + + inline colony_iterator( colony_iterator < !is_const > &&source ) noexcept: + group_pointer( std::move( source.group_pointer ) ), + element_pointer( std::move( source.element_pointer ) ), + skipfield_pointer( std::move( source.skipfield_pointer ) ) {} + }; // colony_iterator + + // Reverse iterators: + template class colony_reverse_iterator + { + private: + iterator it; + + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename colony::value_type; + using difference_type = typename colony::difference_type; + using pointer = typename + choose::type; + using reference = typename + choose::type; + + friend class colony; + + inline colony_reverse_iterator &operator=( const colony_reverse_iterator &source ) noexcept { + it = source.it; + return *this; + } + + // move assignment + inline colony_reverse_iterator &operator=( colony_reverse_iterator &&source ) noexcept { + it = std::move( source.it ); + return *this; + } + + inline COLONY_FORCE_INLINE bool operator==( const colony_reverse_iterator &rh ) const noexcept { + return ( it == rh.it ); + } + + inline COLONY_FORCE_INLINE bool operator!=( const colony_reverse_iterator &rh ) const noexcept { + return ( it != rh.it ); + } + + inline COLONY_FORCE_INLINE reference operator*() const noexcept { + return *( reinterpret_cast( it.element_pointer ) ); + } + + inline COLONY_FORCE_INLINE pointer *operator->() const noexcept { + return reinterpret_cast( it.element_pointer ); + } + + // In this case we have to redefine the algorithm, rather than using the internal iterator's -- operator, in order for the reverse_iterator to be allowed to reach rend() ie. begin_iterator - 1 + colony_reverse_iterator &operator++() { + colony::group_pointer_type &group_pointer = it.group_pointer; + colony::aligned_pointer_type &element_pointer = it.element_pointer; + colony::skipfield_pointer_type &skipfield_pointer = it.skipfield_pointer; + + assert( group_pointer != nullptr ); + assert( !( element_pointer == group_pointer->elements - 1 && + group_pointer->previous_group == nullptr ) ); // Assert that we are not already at rend() + + if( element_pointer != group_pointer->elements ) { // ie. not already at beginning of group + element_pointer -= *( --skipfield_pointer ) + 1; + skipfield_pointer -= *skipfield_pointer; + + if( !( element_pointer == group_pointer->elements - 1 && + group_pointer->previous_group == nullptr ) ) { // i.e. iterator is not == rend() + return *this; + } + } + + if( group_pointer->previous_group != nullptr ) { // i.e. not first group in colony + group_pointer = group_pointer->previous_group; + skipfield_pointer = group_pointer->skipfield + group_pointer->capacity - 1; + element_pointer = ( reinterpret_cast( group_pointer->skipfield ) - 1 ) + - *skipfield_pointer; + skipfield_pointer -= *skipfield_pointer; + } else { // necessary so that reverse_iterator can end up == rend(), if we were already at first element in colony + --element_pointer; + --skipfield_pointer; + } + + return *this; + } + + inline colony_reverse_iterator operator++( int ) { + const colony_reverse_iterator copy( *this ); + ++*this; + return copy; + } + + inline COLONY_FORCE_INLINE colony_reverse_iterator &operator--() { + // i.e. Check that we are not already at rbegin() + assert( !( it.element_pointer == it.group_pointer->last_endpoint - 1 && + it.group_pointer->next_group == nullptr ) ); + ++it; + return *this; + } + + inline colony_reverse_iterator operator--( int ) { + const colony_reverse_iterator copy( *this ); + --*this; + return copy; + } + + inline typename colony::iterator base() const { + return ++( typename colony::iterator( it ) ); + } + + inline bool operator>( const colony_reverse_iterator &rh ) const noexcept { + return ( rh.it > it ); + } + + inline bool operator<( const colony_reverse_iterator &rh ) const noexcept { + return ( it > rh.it ); + } + + inline bool operator>=( const colony_reverse_iterator &rh ) const noexcept { + return !( it > rh.it ); + } + + inline bool operator<=( const colony_reverse_iterator &rh ) const noexcept { + return !( rh.it > it ); + } + + inline COLONY_FORCE_INLINE bool operator==( const colony_reverse_iterator < !r_is_const > &rh ) + const noexcept { + return ( it == rh.it ); + } + + inline COLONY_FORCE_INLINE bool operator!=( const colony_reverse_iterator < !r_is_const > &rh ) + const noexcept { + return ( it != rh.it ); + } + + inline bool operator>( const colony_reverse_iterator < !r_is_const > &rh ) const noexcept { + return ( rh.it > it ); + } + + inline bool operator<( const colony_reverse_iterator < !r_is_const > &rh ) const noexcept { + return ( it > rh.it ); + } + + inline bool operator>=( const colony_reverse_iterator < !r_is_const > &rh ) const noexcept { + return !( it > rh.it ); + } + + inline bool operator<=( const colony_reverse_iterator < !r_is_const > &rh ) const noexcept { + return !( rh.it > it ); + } + + colony_reverse_iterator() noexcept = default; + + colony_reverse_iterator( const colony_reverse_iterator &source ) noexcept: + it( source.it ) {} + + colony_reverse_iterator( const typename colony::iterator &source ) noexcept: + it( source ) {} + + private: + // Used by rend(), etc: + colony_reverse_iterator( const group_pointer_type group_p, const aligned_pointer_type element_p, + const skipfield_pointer_type skipfield_p ) noexcept: + it( group_p, element_p, skipfield_p ) {} + + public: + + // move constructors + colony_reverse_iterator( colony_reverse_iterator &&source ) noexcept: + it( std::move( source.it ) ) {} + + colony_reverse_iterator( typename colony::iterator &&source ) noexcept: + it( std::move( source ) ) {} + + }; // colony_reverse_iterator + + private: + + // Used to prevent fill-insert/constructor calls being mistakenly resolved to range-insert/constructor calls + template + struct enable_if_c { + using type = T; + }; + + template + struct enable_if_c { + }; + + iterator end_iterator, begin_iterator; + + // Head of a singly-linked intrusive list of groups which have erased-element memory locations available for reuse + group_pointer_type groups_with_erasures_list_head; + size_type total_number_of_elements, total_capacity; + + // Packaging the element pointer allocator with a lesser-used member variable, for empty-base-class optimization + struct ebco_pair2 : pointer_allocator_type { + skipfield_type min_elements_per_group; + explicit ebco_pair2( const skipfield_type min_elements ) noexcept: + min_elements_per_group( min_elements ) {} + } pointer_allocator_pair; + + struct ebco_pair : group_allocator_type { + skipfield_type max_elements_per_group; + explicit ebco_pair( const skipfield_type max_elements ) noexcept: + max_elements_per_group( max_elements ) {} + } group_allocator_pair; + + public: + + /** + * Default constructor: + * default minimum group size is 8, default maximum group size is + * std::numeric_limits::max() (typically 65535). You cannot set the group + * sizes from the constructor in this scenario, but you can call the change_group_sizes() + * member function after construction has occurred. + */ + colony() noexcept: + element_allocator_type( element_allocator_type() ), + groups_with_erasures_list_head( nullptr ), + total_number_of_elements( 0 ), + total_capacity( 0 ), + pointer_allocator_pair( ( sizeof( aligned_element_type ) * 8 > ( sizeof( *this ) + sizeof( + group ) ) * 2 ) ? 8 : ( ( ( sizeof( *this ) + sizeof( group ) ) * 2 ) / sizeof( + aligned_element_type ) ) ), + group_allocator_pair( std::numeric_limits::max() ) { + // skipfield type must be of unsigned integer type (uchar, ushort, uint etc) + assert( std::numeric_limits::is_integer & + !std::numeric_limits::is_signed ); + } + + /** + * Default constructor, but using a custom memory allocator eg. something other than + * std::allocator. + */ + explicit colony( const element_allocator_type &alloc ): + element_allocator_type( alloc ), + groups_with_erasures_list_head( NULL ), + total_number_of_elements( 0 ), + total_capacity( 0 ), + pointer_allocator_pair( ( sizeof( aligned_element_type ) * 8 > ( sizeof( *this ) + sizeof( + group ) ) * 2 ) ? 8 : ( ( ( sizeof( *this ) + sizeof( group ) ) * 2 ) / sizeof( + aligned_element_type ) ) ), + group_allocator_pair( std::numeric_limits::max() ) { + assert( std::numeric_limits::is_integer & + !std::numeric_limits::is_signed ); + } + + /** + * Copy constructor: + * Copy all contents from source colony, removes any empty (erased) element locations in the + * process. Size of groups created is either the total size of the source colony, or the + * maximum group size of the source colony, whichever is the smaller. + */ + colony( const colony &source ): + element_allocator_type( source ), + groups_with_erasures_list_head( nullptr ), + total_number_of_elements( 0 ), + total_capacity( 0 ), + // Make the first colony group capacity the greater of min_elements_per_group or total_number_of_elements, so long as total_number_of_elements isn't larger than max_elements_per_group + pointer_allocator_pair( static_cast( ( + source.pointer_allocator_pair.min_elements_per_group > source.total_number_of_elements ) ? + source.pointer_allocator_pair.min_elements_per_group : ( ( source.total_number_of_elements > + source.group_allocator_pair.max_elements_per_group ) ? + source.group_allocator_pair.max_elements_per_group : + source.total_number_of_elements ) ) ), + group_allocator_pair( source.group_allocator_pair.max_elements_per_group ) { + insert( source.begin_iterator, source.end_iterator ); + // reset to correct value for future clear() or erasures + pointer_allocator_pair.min_elements_per_group = + source.pointer_allocator_pair.min_elements_per_group; + } + + // Copy constructor (allocator-extended): + colony( const colony &source, const allocator_type &alloc ): + element_allocator_type( alloc ), + groups_with_erasures_list_head( nullptr ), + total_number_of_elements( 0 ), + total_capacity( 0 ), + pointer_allocator_pair( static_cast( ( + source.pointer_allocator_pair.min_elements_per_group > source.total_number_of_elements ) ? + source.pointer_allocator_pair.min_elements_per_group : ( ( source.total_number_of_elements > + source.group_allocator_pair.max_elements_per_group ) ? + source.group_allocator_pair.max_elements_per_group : source.total_number_of_elements ) ) ), + group_allocator_pair( source.group_allocator_pair.max_elements_per_group ) { + insert( source.begin_iterator, source.end_iterator ); + pointer_allocator_pair.min_elements_per_group = + source.pointer_allocator_pair.min_elements_per_group; + } + + private: + + inline void blank() noexcept { + // if all pointer types are trivial, we can just nuke it from orbit with memset (NULL is always 0 in C++): + if COLONY_CONSTEXPR( std::is_trivial::value && + std::is_trivial::value && + std::is_trivial::value ) { + std::memset( static_cast( this ), 0, offsetof( colony, pointer_allocator_pair ) ); + } else { + end_iterator.group_pointer = nullptr; + end_iterator.element_pointer = nullptr; + end_iterator.skipfield_pointer = nullptr; + begin_iterator.group_pointer = nullptr; + begin_iterator.element_pointer = nullptr; + begin_iterator.skipfield_pointer = nullptr; + groups_with_erasures_list_head = nullptr; + total_number_of_elements = 0; + total_capacity = 0; + } + } + + public: + + /** + * Move constructor: + * Move all contents from source colony, does not remove any erased element locations or + * alter any of the source group sizes. Source colony is now empty and can be safely + * destructed or otherwise used. + */ + colony( colony &&source ) noexcept: + element_allocator_type( source ), + end_iterator( std::move( source.end_iterator ) ), + begin_iterator( std::move( source.begin_iterator ) ), + groups_with_erasures_list_head( std::move( source.groups_with_erasures_list_head ) ), + total_number_of_elements( source.total_number_of_elements ), + total_capacity( source.total_capacity ), + pointer_allocator_pair( source.pointer_allocator_pair.min_elements_per_group ), + group_allocator_pair( source.group_allocator_pair.max_elements_per_group ) { + source.blank(); + } + + // Move constructor (allocator-extended): + colony( colony &&source, const allocator_type &alloc ): + element_allocator_type( alloc ), + end_iterator( std::move( source.end_iterator ) ), + begin_iterator( std::move( source.begin_iterator ) ), + groups_with_erasures_list_head( std::move( source.groups_with_erasures_list_head ) ), + total_number_of_elements( source.total_number_of_elements ), + total_capacity( source.total_capacity ), + pointer_allocator_pair( source.pointer_allocator_pair.min_elements_per_group ), + group_allocator_pair( source.group_allocator_pair.max_elements_per_group ) { + source.blank(); + } + + /** + * Fill constructor with value_type unspecified, so the value_type's default constructor is + * used. n specifies the number of elements to create upon construction. If n is larger than + * min_group_size, the size of the groups created will either be n and max_group_size, + * depending on which is smaller. min_group_size (i.e. the smallest possible number of + * elements which can be stored in a colony group) can be defined, as can the max_group_size. + * Setting the group sizes can be a performance advantage if you know in advance roughly how + * many objects are likely to be stored in your colony long-term - or at least the rough + * scale of storage. If that case, using this can stop many small initial groups being + * allocated (reserve() will achieve a similar result, but structurally at the moment is + * limited to allocating one group). + */ + colony( const size_type fill_number, const element_type &element, + const skipfield_type min_allocation_amount = 0, + const skipfield_type max_allocation_amount = std::numeric_limits::max(), + const element_allocator_type &alloc = element_allocator_type() ): + element_allocator_type( alloc ), + groups_with_erasures_list_head( nullptr ), + total_number_of_elements( 0 ), + total_capacity( 0 ), + pointer_allocator_pair( ( min_allocation_amount != 0 ) ? min_allocation_amount : + ( fill_number > max_allocation_amount ) ? max_allocation_amount : + ( fill_number > 8 ) ? static_cast( fill_number ) : 8 ), + group_allocator_pair( max_allocation_amount ) { + assert( std::numeric_limits::is_integer & + !std::numeric_limits::is_signed ); + assert( ( pointer_allocator_pair.min_elements_per_group > 2 ) & + ( pointer_allocator_pair.min_elements_per_group <= group_allocator_pair.max_elements_per_group ) ); + + insert( fill_number, element ); + } + + // Range constructor: + template + colony( const typename enable_if_c < !std::numeric_limits::is_integer, + iterator_type >::type &first, const iterator_type &last, + const skipfield_type min_allocation_amount = 8, + const skipfield_type max_allocation_amount = std::numeric_limits::max(), + const element_allocator_type &alloc = element_allocator_type() ): + element_allocator_type( alloc ), + groups_with_erasures_list_head( nullptr ), + total_number_of_elements( 0 ), + total_capacity( 0 ), + pointer_allocator_pair( min_allocation_amount ), + group_allocator_pair( max_allocation_amount ) { + assert( std::numeric_limits::is_integer & + !std::numeric_limits::is_signed ); + assert( ( pointer_allocator_pair.min_elements_per_group > 2 ) & + ( pointer_allocator_pair.min_elements_per_group <= group_allocator_pair.max_elements_per_group ) ); + + insert( first, last ); + } + + // Initializer-list constructor: + colony( const std::initializer_list &element_list, + const skipfield_type min_allocation_amount = 0, + const skipfield_type max_allocation_amount = std::numeric_limits::max(), + const element_allocator_type &alloc = element_allocator_type() ): + element_allocator_type( alloc ), + groups_with_erasures_list_head( nullptr ), + total_number_of_elements( 0 ), + total_capacity( 0 ), + pointer_allocator_pair( ( min_allocation_amount != 0 ) ? min_allocation_amount : + ( element_list.size() > max_allocation_amount ) ? max_allocation_amount : + ( element_list.size() > 8 ) ? static_cast( element_list.size() ) : 8 ), + group_allocator_pair( max_allocation_amount ) { + assert( std::numeric_limits::is_integer & + !std::numeric_limits::is_signed ); + assert( ( pointer_allocator_pair.min_elements_per_group > 2 ) & + ( pointer_allocator_pair.min_elements_per_group <= group_allocator_pair.max_elements_per_group ) ); + + insert( element_list ); + } + + inline COLONY_FORCE_INLINE iterator begin() noexcept { + return begin_iterator; + } + + inline COLONY_FORCE_INLINE const iterator &begin() const + noexcept { // To allow for functions which only take const colony & as a source eg. copy constructor + return begin_iterator; + } + + inline COLONY_FORCE_INLINE iterator end() noexcept { + return end_iterator; + } + + inline COLONY_FORCE_INLINE const iterator &end() const noexcept { + return end_iterator; + } + + inline const_iterator cbegin() const noexcept { + return const_iterator( begin_iterator.group_pointer, begin_iterator.element_pointer, + begin_iterator.skipfield_pointer ); + } + + inline const_iterator cend() const noexcept { + return const_iterator( end_iterator.group_pointer, end_iterator.element_pointer, + end_iterator.skipfield_pointer ); + } + + inline reverse_iterator rbegin() + const { // May throw exception if colony is empty so end_iterator is uninitialized + return ++reverse_iterator( end_iterator ); + } + + inline reverse_iterator rend() const noexcept { + return reverse_iterator( begin_iterator.group_pointer, begin_iterator.element_pointer - 1, + begin_iterator.skipfield_pointer - 1 ); + } + + inline const_reverse_iterator crbegin() const { + return ++const_reverse_iterator( end_iterator ); + } + + inline const_reverse_iterator crend() const noexcept { + return const_reverse_iterator( begin_iterator.group_pointer, begin_iterator.element_pointer - 1, + begin_iterator.skipfield_pointer - 1 ); + } + + ~colony() noexcept { + destroy_all_data(); + } + + private: + + void destroy_all_data() noexcept { + // Amusingly enough, these changes from && to logical & actually do make a significant difference in debug mode + if( ( total_number_of_elements != 0 ) & !( std::is_trivially_destructible::value ) ) { + total_number_of_elements = 0; // to avoid double-destruction + + while( true ) { + const aligned_pointer_type end_pointer = begin_iterator.group_pointer->last_endpoint; + + do { + COLONY_DESTROY( element_allocator_type, ( *this ), + reinterpret_cast( begin_iterator.element_pointer ) ); + ++begin_iterator.skipfield_pointer; + begin_iterator.element_pointer += *begin_iterator.skipfield_pointer + 1; + begin_iterator.skipfield_pointer += *begin_iterator.skipfield_pointer; + } while( begin_iterator.element_pointer != end_pointer ); // ie. beyond end of available data + + const group_pointer_type next_group = begin_iterator.group_pointer->next_group; + COLONY_DESTROY( group_allocator_type, group_allocator_pair, begin_iterator.group_pointer ); + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, begin_iterator.group_pointer, 1 ); + begin_iterator.group_pointer = + next_group; // required to be before if statement in order for first_group to be NULL and avoid potential double-destruction in future + + if( next_group == nullptr ) { + return; + } + + begin_iterator.element_pointer = next_group->elements + *( next_group->skipfield ); + begin_iterator.skipfield_pointer = next_group->skipfield + *( next_group->skipfield ); + } + } else { // Avoid iteration for both empty groups and trivially-destructible types eg. POD, structs, classes with empty destructors + // Technically under a type-traits-supporting compiler total_number_of_elements could be non-zero at this point, but since begin_iterator.group_pointer would already be NULL in the case of double-destruction, it's unnecessary to zero total_number_of_elements + while( begin_iterator.group_pointer != nullptr ) { + const group_pointer_type next_group = begin_iterator.group_pointer->next_group; + COLONY_DESTROY( group_allocator_type, group_allocator_pair, begin_iterator.group_pointer ); + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, begin_iterator.group_pointer, 1 ); + begin_iterator.group_pointer = next_group; + } + } + } + + void initialize( const skipfield_type first_group_size ) { + begin_iterator.group_pointer = COLONY_ALLOCATE( group_allocator_type, group_allocator_pair, 1, + nullptr ); + + try { + COLONY_CONSTRUCT( group_allocator_type, group_allocator_pair, begin_iterator.group_pointer, + first_group_size ); + } catch( ... ) { + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, begin_iterator.group_pointer, 1 ); + begin_iterator.group_pointer = nullptr; + throw; + } + + end_iterator.group_pointer = begin_iterator.group_pointer; + end_iterator.element_pointer = begin_iterator.element_pointer = + begin_iterator.group_pointer->elements; + end_iterator.skipfield_pointer = begin_iterator.skipfield_pointer = + begin_iterator.group_pointer->skipfield; + total_capacity = first_group_size; + } + + public: + + /** + * Inserts the element supplied to the colony, using the object's copy-constructor. Will + * insert the element into a previously erased element slot if one exists, otherwise will + * insert to back of colony. Returns iterator to location of inserted element. + */ + iterator insert( const element_type &element ) { + if( end_iterator.element_pointer != nullptr ) { + switch( ( ( groups_with_erasures_list_head != nullptr ) << 1 ) | ( end_iterator.element_pointer == + reinterpret_cast( end_iterator.group_pointer->skipfield ) ) ) { + case 0: { // ie. there are no erased elements and end_iterator is not at end of current final group + // Make copy for return before modifying end_iterator + const iterator return_iterator = end_iterator; + + if COLONY_CONSTEXPR( std::is_nothrow_copy_constructible::value ) { + // For no good reason this compiles to faster code under GCC: + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( end_iterator.element_pointer++ ), element ); + end_iterator.group_pointer->last_endpoint = end_iterator.element_pointer; + } else { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( end_iterator.element_pointer ), element ); + // Shift the addition to the second operation, avoiding problems if an exception is thrown during construction + end_iterator.group_pointer->last_endpoint = ++end_iterator.element_pointer; + } + + ++( end_iterator.group_pointer->number_of_elements ); + ++end_iterator.skipfield_pointer; + ++total_number_of_elements; + + return return_iterator; // return value before incrementation + } + case 1: { // ie. there are no erased elements and end_iterator is at end of current final group - ie. colony is full - create new group + end_iterator.group_pointer->next_group = COLONY_ALLOCATE( group_allocator_type, + group_allocator_pair, 1, end_iterator.group_pointer ); + group &next_group = *( end_iterator.group_pointer->next_group ); + const skipfield_type new_group_size = ( total_number_of_elements < static_cast + ( group_allocator_pair.max_elements_per_group ) ) ? static_cast + ( total_number_of_elements ) : group_allocator_pair.max_elements_per_group; + + try { + COLONY_CONSTRUCT( group_allocator_type, group_allocator_pair, &next_group, new_group_size, + end_iterator.group_pointer ); + } catch( ... ) { + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, &next_group, 1 ); + end_iterator.group_pointer->next_group = nullptr; + throw; + } + + if COLONY_CONSTEXPR( std::is_nothrow_copy_constructible::value ) { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( next_group.elements ), element ); + } else { + try { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( next_group.elements ), element ); + } catch( ... ) { + COLONY_DESTROY( group_allocator_type, group_allocator_pair, &next_group ); + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, &next_group, 1 ); + end_iterator.group_pointer->next_group = nullptr; + throw; + } + } + + end_iterator.group_pointer = &next_group; + end_iterator.element_pointer = next_group.last_endpoint; + end_iterator.skipfield_pointer = next_group.skipfield + 1; + ++total_number_of_elements; + total_capacity += new_group_size; + + // returns value before incrementation + return iterator( end_iterator.group_pointer, next_group.elements, next_group.skipfield ); + } + default: { // ie. there are erased elements, reuse previous-erased element locations + iterator new_location; + new_location.group_pointer = groups_with_erasures_list_head; + new_location.element_pointer = groups_with_erasures_list_head->elements + + groups_with_erasures_list_head->free_list_head; + new_location.skipfield_pointer = groups_with_erasures_list_head->skipfield + + groups_with_erasures_list_head->free_list_head; + + // always at start of skipblock, update skipblock: + const skipfield_type prev_free_list_index = *( reinterpret_cast + ( new_location.element_pointer ) ); + + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( new_location.element_pointer ), element ); + + // Update skipblock: + const skipfield_type new_value = *( new_location.skipfield_pointer ) - 1; + + // ie. skipfield was not 1, ie. a single-node skipblock, with no additional nodes to update + if( new_value != 0 ) { + // set (new) start and (original) end of skipblock to new value: + *( new_location.skipfield_pointer + new_value ) = *( new_location.skipfield_pointer + 1 ) = + new_value; + + // transfer free list node to new start node: + ++( groups_with_erasures_list_head->free_list_head ); + + // i.e. not the tail free list node + if( prev_free_list_index != std::numeric_limits::max() ) { + *( reinterpret_cast( new_location.group_pointer->elements + + prev_free_list_index ) + 1 ) = groups_with_erasures_list_head->free_list_head; + } + + *( reinterpret_cast( new_location.element_pointer + 1 ) ) = + prev_free_list_index; + *( reinterpret_cast( new_location.element_pointer + 1 ) + 1 ) = + std::numeric_limits::max(); + } else { + groups_with_erasures_list_head->free_list_head = prev_free_list_index; + + // i.e. not the last free list node + if( prev_free_list_index != std::numeric_limits::max() ) { + *( reinterpret_cast( new_location.group_pointer->elements + + prev_free_list_index ) + 1 ) = std::numeric_limits::max(); + } else { + groups_with_erasures_list_head = groups_with_erasures_list_head->erasures_list_next_group; + } + } + + *( new_location.skipfield_pointer ) = 0; + ++( new_location.group_pointer->number_of_elements ); + + if( new_location.group_pointer == begin_iterator.group_pointer && + new_location.element_pointer < begin_iterator.element_pointer ) { + // i.e. begin_iterator was moved forwards as the result of an erasure at some point, this erased element is before the current begin, hence, set current begin iterator to this element + begin_iterator = new_location; + } + + ++total_number_of_elements; + return new_location; + } + } + } else { // i.e. newly-constructed colony, no insertions yet and no groups + initialize( pointer_allocator_pair.min_elements_per_group ); + + if COLONY_CONSTEXPR( std::is_nothrow_copy_constructible::value ) { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( end_iterator.element_pointer++ ), element ); + } else { + try { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( end_iterator.element_pointer++ ), element ); + } catch( ... ) { + clear(); + throw; + } + } + + ++end_iterator.skipfield_pointer; + total_number_of_elements = 1; + return begin_iterator; + } + } + + /** + * Moves the element supplied to the colony, using the object's move-constructor. Will + * insert the element in a previously erased element slot if one exists, otherwise will + * insert to back of colony. Returns iterator to location of inserted element. + * + * The move-insert function is near-identical to the regular insert function, with the + * exception of the element construction method and is_nothrow tests. + */ + iterator insert( element_type &&element ) { + if( end_iterator.element_pointer != nullptr ) { + switch( ( ( groups_with_erasures_list_head != nullptr ) << 1 ) | ( end_iterator.element_pointer == + reinterpret_cast( end_iterator.group_pointer->skipfield ) ) ) { + case 0: { + const iterator return_iterator = end_iterator; + + if COLONY_CONSTEXPR( std::is_nothrow_move_constructible::value ) { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( end_iterator.element_pointer++ ), std::move( element ) ); + end_iterator.group_pointer->last_endpoint = end_iterator.element_pointer; + } else { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( end_iterator.element_pointer ), std::move( element ) ); + end_iterator.group_pointer->last_endpoint = ++end_iterator.element_pointer; + } + + ++( end_iterator.group_pointer->number_of_elements ); + ++end_iterator.skipfield_pointer; + ++total_number_of_elements; + + return return_iterator; + } + case 1: { + end_iterator.group_pointer->next_group = COLONY_ALLOCATE( group_allocator_type, + group_allocator_pair, 1, end_iterator.group_pointer ); + group &next_group = *( end_iterator.group_pointer->next_group ); + const skipfield_type new_group_size = ( total_number_of_elements < static_cast + ( group_allocator_pair.max_elements_per_group ) ) ? static_cast + ( total_number_of_elements ) : group_allocator_pair.max_elements_per_group; + + try { + COLONY_CONSTRUCT( group_allocator_type, group_allocator_pair, &next_group, new_group_size, + end_iterator.group_pointer ); + } catch( ... ) { + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, &next_group, 1 ); + end_iterator.group_pointer->next_group = nullptr; + throw; + } + + if COLONY_CONSTEXPR( std::is_nothrow_move_constructible::value ) { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( next_group.elements ), std::move( element ) ); + } else { + try { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( next_group.elements ), std::move( element ) ); + } catch( ... ) { + COLONY_DESTROY( group_allocator_type, group_allocator_pair, &next_group ); + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, &next_group, 1 ); + end_iterator.group_pointer->next_group = nullptr; + throw; + } + } + + end_iterator.group_pointer = &next_group; + end_iterator.element_pointer = next_group.last_endpoint; + end_iterator.skipfield_pointer = next_group.skipfield + 1; + ++total_number_of_elements; + total_capacity += new_group_size; + + return iterator( end_iterator.group_pointer, next_group.elements, next_group.skipfield ); + } + default: { + iterator new_location; + new_location.group_pointer = groups_with_erasures_list_head; + new_location.element_pointer = groups_with_erasures_list_head->elements + + groups_with_erasures_list_head->free_list_head; + new_location.skipfield_pointer = groups_with_erasures_list_head->skipfield + + groups_with_erasures_list_head->free_list_head; + + const skipfield_type prev_free_list_index = *( reinterpret_cast + ( new_location.element_pointer ) ); + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( new_location.element_pointer ), std::move( element ) ); + + const skipfield_type new_value = *( new_location.skipfield_pointer ) - 1; + + if( new_value != 0 ) { + *( new_location.skipfield_pointer + new_value ) = *( new_location.skipfield_pointer + 1 ) = + new_value; + ++( groups_with_erasures_list_head->free_list_head ); + + if( prev_free_list_index != std::numeric_limits::max() ) { + *( reinterpret_cast( new_location.group_pointer->elements + + prev_free_list_index ) + 1 ) = groups_with_erasures_list_head->free_list_head; + } + + *( reinterpret_cast( new_location.element_pointer + 1 ) ) = + prev_free_list_index; + *( reinterpret_cast( new_location.element_pointer + 1 ) + 1 ) = + std::numeric_limits::max(); + } else { + groups_with_erasures_list_head->free_list_head = prev_free_list_index; + + if( prev_free_list_index != std::numeric_limits::max() ) { + *( reinterpret_cast( new_location.group_pointer->elements + + prev_free_list_index ) + 1 ) = std::numeric_limits::max(); + } else { + groups_with_erasures_list_head = groups_with_erasures_list_head->erasures_list_next_group; + } + } + + *( new_location.skipfield_pointer ) = 0; + ++( new_location.group_pointer->number_of_elements ); + + if( new_location.group_pointer == begin_iterator.group_pointer && + new_location.element_pointer < begin_iterator.element_pointer ) { + begin_iterator = new_location; + } + + ++total_number_of_elements; + + return new_location; + } + } + } else { + initialize( pointer_allocator_pair.min_elements_per_group ); + + if COLONY_CONSTEXPR( std::is_nothrow_move_constructible::value ) { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( end_iterator.element_pointer++ ), std::move( element ) ); + } else { + try { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( end_iterator.element_pointer++ ), std::move( element ) ); + } catch( ... ) { + clear(); + throw; + } + } + + ++end_iterator.skipfield_pointer; + total_number_of_elements = 1; + return begin_iterator; + } + } + + /** + * Constructs new element directly within colony. Will insert the element in a previously + * erased element slot if one exists, otherwise will insert to back of colony. Returns + * iterator to location of inserted element. "...parameters" are whatever parameters are + * required by the object's constructor. + * + * The emplace function is near-identical to the regular insert function, with the exception + * of the element construction method and change to is_nothrow tests. + */ + template + iterator emplace( arguments &&... parameters ) { + if( end_iterator.element_pointer != nullptr ) { + switch( ( ( groups_with_erasures_list_head != nullptr ) << 1 ) | ( end_iterator.element_pointer == + reinterpret_cast( end_iterator.group_pointer->skipfield ) ) ) { + case 0: { + const iterator return_iterator = end_iterator; + + if COLONY_CONSTEXPR( std::is_nothrow_constructible::value ) { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( end_iterator.element_pointer++ ), + std::forward( parameters )... ); + end_iterator.group_pointer->last_endpoint = end_iterator.element_pointer; + } else { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( end_iterator.element_pointer ), + std::forward( parameters )... ); + end_iterator.group_pointer->last_endpoint = ++end_iterator.element_pointer; + } + + ++( end_iterator.group_pointer->number_of_elements ); + ++end_iterator.skipfield_pointer; + ++total_number_of_elements; + + return return_iterator; + } + case 1: { + end_iterator.group_pointer->next_group = COLONY_ALLOCATE( group_allocator_type, + group_allocator_pair, 1, end_iterator.group_pointer ); + group &next_group = *( end_iterator.group_pointer->next_group ); + const skipfield_type new_group_size = ( total_number_of_elements < static_cast + ( group_allocator_pair.max_elements_per_group ) ) ? static_cast + ( total_number_of_elements ) : group_allocator_pair.max_elements_per_group; + + try { + COLONY_CONSTRUCT( group_allocator_type, group_allocator_pair, &next_group, new_group_size, + end_iterator.group_pointer ); + } catch( ... ) { + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, &next_group, 1 ); + end_iterator.group_pointer->next_group = nullptr; + throw; + } + + if COLONY_CONSTEXPR( std::is_nothrow_constructible::value ) { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( next_group.elements ), std::forward( parameters )... ); + } else { + try { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( next_group.elements ), std::forward( parameters )... ); + } catch( ... ) { + COLONY_DESTROY( group_allocator_type, group_allocator_pair, &next_group ); + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, &next_group, 1 ); + end_iterator.group_pointer->next_group = nullptr; + throw; + } + } + + end_iterator.group_pointer = &next_group; + end_iterator.element_pointer = next_group.last_endpoint; + end_iterator.skipfield_pointer = next_group.skipfield + 1; + total_capacity += new_group_size; + ++total_number_of_elements; + + return iterator( end_iterator.group_pointer, next_group.elements, next_group.skipfield ); + } + default: { + iterator new_location; + new_location.group_pointer = groups_with_erasures_list_head; + new_location.element_pointer = groups_with_erasures_list_head->elements + + groups_with_erasures_list_head->free_list_head; + new_location.skipfield_pointer = groups_with_erasures_list_head->skipfield + + groups_with_erasures_list_head->free_list_head; + + const skipfield_type prev_free_list_index = *( reinterpret_cast + ( new_location.element_pointer ) ); + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( new_location.element_pointer ), + std::forward( parameters ) ... ); + const skipfield_type new_value = *( new_location.skipfield_pointer ) - 1; + + if( new_value != 0 ) { + *( new_location.skipfield_pointer + new_value ) = *( new_location.skipfield_pointer + 1 ) = + new_value; + ++( groups_with_erasures_list_head->free_list_head ); + + if( prev_free_list_index != std::numeric_limits::max() ) { + *( reinterpret_cast( new_location.group_pointer->elements + + prev_free_list_index ) + 1 ) = groups_with_erasures_list_head->free_list_head; + } + + *( reinterpret_cast( new_location.element_pointer + 1 ) ) = + prev_free_list_index; + *( reinterpret_cast( new_location.element_pointer + 1 ) + 1 ) = + std::numeric_limits::max(); + } else { + groups_with_erasures_list_head->free_list_head = prev_free_list_index; + + if( prev_free_list_index != std::numeric_limits::max() ) { + *( reinterpret_cast( new_location.group_pointer->elements + + prev_free_list_index ) + 1 ) = std::numeric_limits::max(); + } else { + groups_with_erasures_list_head = groups_with_erasures_list_head->erasures_list_next_group; + } + } + + *( new_location.skipfield_pointer ) = 0; + ++( new_location.group_pointer->number_of_elements ); + + if( new_location.group_pointer == begin_iterator.group_pointer && + new_location.element_pointer < begin_iterator.element_pointer ) { + begin_iterator = new_location; + } + + ++total_number_of_elements; + + return new_location; + } + } + } else { + initialize( pointer_allocator_pair.min_elements_per_group ); + + if COLONY_CONSTEXPR( std::is_nothrow_constructible::value ) { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( end_iterator.element_pointer++ ), + std::forward( parameters ) ... ); + } else { + try { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( end_iterator.element_pointer++ ), + std::forward( parameters ) ... ); + } catch( ... ) { + clear(); + throw; + } + } + + ++end_iterator.skipfield_pointer; + total_number_of_elements = 1; + return begin_iterator; + } + } + + private: + + // Internal functions for fill insert: + void group_create( const skipfield_type number_of_elements ) { + const group_pointer_type next_group = end_iterator.group_pointer->next_group = COLONY_ALLOCATE( + group_allocator_type, group_allocator_pair, 1, end_iterator.group_pointer ); + + try { + COLONY_CONSTRUCT( group_allocator_type, group_allocator_pair, next_group, number_of_elements, + end_iterator.group_pointer ); + } catch( ... ) { + COLONY_DESTROY( group_allocator_type, group_allocator_pair, next_group ); + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, next_group, 1 ); + end_iterator.group_pointer->next_group = nullptr; + throw; + } + + end_iterator.group_pointer = next_group; + end_iterator.element_pointer = next_group->elements; + // group constructor sets this to 1 by default to allow for faster insertion during insertion/emplace in other cases + next_group->number_of_elements = 0; + total_capacity += number_of_elements; + } + + void group_fill( const element_type &element, const skipfield_type number_of_elements ) { + // ie. we can get away with using the cheaper fill_n here if there is no chance of an exception being thrown: + if COLONY_CONSTEXPR( std::is_trivially_copyable::value && + std::is_trivially_copy_constructible::value && + std::is_nothrow_copy_constructible::value ) { + if COLONY_CONSTEXPR( sizeof( aligned_element_type ) == sizeof( element_type ) ) { + std::fill_n( reinterpret_cast( end_iterator.element_pointer ), number_of_elements, + element ); + } else { + // to avoid potentially violating memory boundaries in line below, create an initial copy object of same (but aligned) type + alignas( sizeof( aligned_element_type ) ) element_type aligned_copy = element; + std::fill_n( end_iterator.element_pointer, number_of_elements, + *( reinterpret_cast( &aligned_copy ) ) ); + } + + end_iterator.element_pointer += number_of_elements; + } else { + const aligned_pointer_type fill_end = end_iterator.element_pointer + number_of_elements; + + do { + try { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), + reinterpret_cast( end_iterator.element_pointer++ ), element ); + } catch( ... ) { + end_iterator.group_pointer->last_endpoint = --end_iterator.element_pointer; + const skipfield_type elements_constructed_before_exception = static_cast + ( end_iterator.element_pointer - end_iterator.group_pointer->elements ); + end_iterator.group_pointer->number_of_elements = elements_constructed_before_exception; + end_iterator.skipfield_pointer = end_iterator.group_pointer->skipfield + + elements_constructed_before_exception; + throw; + } + } while( end_iterator.element_pointer != fill_end ); + } + + end_iterator.group_pointer->last_endpoint = end_iterator.element_pointer; + end_iterator.group_pointer->number_of_elements += number_of_elements; + } + + void fill_skipblock( const element_type &element, aligned_pointer_type const location, + skipfield_pointer_type const skipfield_pointer, const skipfield_type number_of_elements ) { + // ie. we can get away with using the cheaper fill_n here if there is no chance of an exception being thrown: + if COLONY_CONSTEXPR( std::is_trivially_copyable::value && + std::is_trivially_copy_constructible::value && + std::is_nothrow_copy_constructible::value ) { + if COLONY_CONSTEXPR( sizeof( aligned_element_type ) == sizeof( element_type ) ) { + std::fill_n( reinterpret_cast( location ), number_of_elements, element ); + } else { + // to avoid potentially violating memory boundaries in line below, create an initial copy object of same (but aligned) type + alignas( sizeof( aligned_element_type ) ) element_type aligned_copy = element; + std::fill_n( location, number_of_elements, + *( reinterpret_cast( &aligned_copy ) ) ); + } + } else { + // in case of exception, grabbing indexes before free_list node is reused + const skipfield_type prev_free_list_node = *( reinterpret_cast + ( location ) ); + const aligned_pointer_type fill_end = location + number_of_elements; + + for( aligned_pointer_type current_location = location; current_location != fill_end; + ++current_location ) { + try { + COLONY_CONSTRUCT( element_allocator_type, ( *this ), reinterpret_cast( current_location ), + element ); + } catch( ... ) { + // Reconstruct existing skipblock and free-list indexes to reflect partially-reused skipblock: + const skipfield_type elements_constructed_before_exception = static_cast( ( + current_location - 1 ) - location ); + groups_with_erasures_list_head->number_of_elements += elements_constructed_before_exception; + total_number_of_elements += elements_constructed_before_exception; + + std::memset( skipfield_pointer, 0, + elements_constructed_before_exception * sizeof( skipfield_type ) ); + + *( reinterpret_cast( location + elements_constructed_before_exception ) ) = + prev_free_list_node; + *( reinterpret_cast( location + elements_constructed_before_exception ) + + 1 ) = std::numeric_limits::max(); + + const skipfield_type new_skipblock_head_index = static_cast + ( location - groups_with_erasures_list_head->elements ) + elements_constructed_before_exception; + groups_with_erasures_list_head->free_list_head = new_skipblock_head_index; + + if( prev_free_list_node != std::numeric_limits::max() ) { + *( reinterpret_cast( groups_with_erasures_list_head->elements + + prev_free_list_node ) + 1 ) = new_skipblock_head_index; + } + + throw; + } + } + } + + // reset skipfield nodes within skipblock to 0 + std::memset( skipfield_pointer, 0, number_of_elements * sizeof( skipfield_type ) ); + groups_with_erasures_list_head->number_of_elements += number_of_elements; + total_number_of_elements += number_of_elements; + } + + public: + + /** + * Fill insert: + * Inserts n copies of val into the colony. Will insert the element into a previously erased + * element slot if one exists, otherwise will insert to back of colony. + */ + void insert( size_type number_of_elements, const element_type &element ) { + if( number_of_elements == 0 ) { + return; + } else if( number_of_elements == 1 ) { + insert( element ); + return; + } + + if( begin_iterator.group_pointer == nullptr ) { // Empty colony, no groups created yet + initialize( ( number_of_elements > group_allocator_pair.max_elements_per_group ) ? + group_allocator_pair.max_elements_per_group : ( number_of_elements < + pointer_allocator_pair.min_elements_per_group ) ? pointer_allocator_pair.min_elements_per_group : + static_cast( number_of_elements ) ); // Construct first group + begin_iterator.group_pointer->number_of_elements = 0; + } + + // ie. not an uninitialized colony or a situation where reserve has been called + if( total_number_of_elements != 0 ) { + // Use up erased locations if available: + if( groups_with_erasures_list_head != nullptr ) { + do { // skipblock loop: breaks when group is exhausted of reusable skipblocks, or returns if number_of_elements == 0 + aligned_pointer_type const element_pointer = groups_with_erasures_list_head->elements + + groups_with_erasures_list_head->free_list_head; + skipfield_pointer_type const skipfield_pointer = groups_with_erasures_list_head->skipfield + + groups_with_erasures_list_head->free_list_head; + const skipfield_type skipblock_size = *skipfield_pointer; + + if( groups_with_erasures_list_head == begin_iterator.group_pointer && + element_pointer < begin_iterator.element_pointer ) { + begin_iterator.element_pointer = element_pointer; + begin_iterator.skipfield_pointer = skipfield_pointer; + } + + if( skipblock_size <= number_of_elements ) { + // set free list head to previous free list node + groups_with_erasures_list_head->free_list_head = *( reinterpret_cast + ( element_pointer ) ); + fill_skipblock( element, element_pointer, skipfield_pointer, skipblock_size ); + number_of_elements -= skipblock_size; + + if( groups_with_erasures_list_head->free_list_head != std::numeric_limits::max() ) { + // set 'next' index of new free list head to 'end' (numeric max) + *( reinterpret_cast( groups_with_erasures_list_head->elements + + groups_with_erasures_list_head->free_list_head ) + 1 ) = std::numeric_limits::max(); + } else { + // change groups + groups_with_erasures_list_head = groups_with_erasures_list_head->erasures_list_next_group; + + if( groups_with_erasures_list_head == nullptr ) { + break; + } + } + } else { // skipblock is larger than remaining number of elements + // save before element location is overwritten + const skipfield_type prev_index = *( reinterpret_cast( element_pointer ) ); + fill_skipblock( element, element_pointer, skipfield_pointer, + static_cast( number_of_elements ) ); + const skipfield_type new_skipblock_size = static_cast( skipblock_size - + number_of_elements ); + + // Update skipfield (earlier nodes already memset'd in fill_skipblock function): + *( skipfield_pointer + number_of_elements ) = new_skipblock_size; + *( skipfield_pointer + skipblock_size - 1 ) = new_skipblock_size; + // set free list head to new start node + groups_with_erasures_list_head->free_list_head += static_cast( number_of_elements ); + + // Update free list with new head: + *( reinterpret_cast( element_pointer + number_of_elements ) ) = prev_index; + *( reinterpret_cast( element_pointer + number_of_elements ) + 1 ) = + std::numeric_limits::max(); + + if( prev_index != std::numeric_limits::max() ) { + // set 'next' index of previous skipblock to new start of skipblock + *( reinterpret_cast( groups_with_erasures_list_head->elements + prev_index ) + + 1 ) = groups_with_erasures_list_head->free_list_head; + } + + return; + } + } while( number_of_elements != 0 ); + } + + // Use up remaining available element locations in end group: + const skipfield_type group_remainder = ( static_cast + ( reinterpret_cast( end_iterator.group_pointer->skipfield ) - + end_iterator.element_pointer ) > number_of_elements ) ? static_cast + ( number_of_elements ) : static_cast( reinterpret_cast + ( end_iterator.group_pointer->skipfield ) - end_iterator.element_pointer ); + + if( group_remainder != 0 ) { + group_fill( element, group_remainder ); + total_number_of_elements += group_remainder; + number_of_elements -= group_remainder; + } + } else if( end_iterator.group_pointer->capacity >= number_of_elements ) { + group_fill( element, static_cast( number_of_elements ) ); + end_iterator.skipfield_pointer = end_iterator.group_pointer->skipfield + number_of_elements; + total_number_of_elements = number_of_elements; + return; + } else { + group_fill( element, end_iterator.group_pointer->capacity ); + total_number_of_elements = end_iterator.group_pointer->capacity; + number_of_elements -= end_iterator.group_pointer->capacity; + } + + // If there's some elements left that need to be created, create new groups and fill: + if( number_of_elements > group_allocator_pair.max_elements_per_group ) { + size_type multiples = ( number_of_elements / static_cast + ( group_allocator_pair.max_elements_per_group ) ); + const skipfield_type element_remainder = static_cast( number_of_elements - + ( multiples * static_cast( group_allocator_pair.max_elements_per_group ) ) ); + + while( multiples-- != 0 ) { + group_create( group_allocator_pair.max_elements_per_group ); + group_fill( element, group_allocator_pair.max_elements_per_group ); + } + + if( element_remainder != 0 ) { + group_create( group_allocator_pair.max_elements_per_group ); + group_fill( element, element_remainder ); + } + } else if( number_of_elements != 0 ) { + group_create( static_cast( ( number_of_elements > total_number_of_elements ) ? + number_of_elements : total_number_of_elements ) ); + group_fill( element, static_cast( number_of_elements ) ); + } + + // Adds the remainder from the last if-block - the insert functions in the first if/else block will already have incremented total_number_of_elements + total_number_of_elements += number_of_elements; + end_iterator.skipfield_pointer = end_iterator.group_pointer->skipfield + + ( end_iterator.element_pointer - end_iterator.group_pointer->elements ); + } + + /** + * Range insert: + * Inserts a series of value_type elements from an external source into a colony holding the + * same value_type (eg. int, float, a particular class, etcetera). Stops inserting once it + * reaches last. + */ + template + inline void insert( typename enable_if_c < !std::numeric_limits::is_integer, + iterator_type >::type first, const iterator_type last ) { + while( first != last ) { + insert( *first++ ); + } + } + + /** + * Initializer-list insert: + * Copies elements from an initializer list into the colony. Will insert the element in a + * previously erased element slot if one exists, otherwise will insert to back of colony. + */ + inline void insert( const std::initializer_list &element_list ) { + // use range insert: + insert( element_list.begin(), element_list.end() ); + } + + private: + + inline COLONY_FORCE_INLINE void update_subsequent_group_numbers( group_pointer_type current_group ) + noexcept { + do { + --( current_group->group_number ); + current_group = current_group->next_group; + } while( current_group != nullptr ); + } + + // get all elements contiguous in memory and shrink to fit, remove erasures and erasure free lists + inline COLONY_FORCE_INLINE void consolidate() { + colony temp; + // Make first allocated group as large total number of elements, where possible + temp.change_group_sizes( static_cast( ( + pointer_allocator_pair.min_elements_per_group > total_number_of_elements ) ? + pointer_allocator_pair.min_elements_per_group : ( ( total_number_of_elements > + group_allocator_pair.max_elements_per_group ) ? group_allocator_pair.max_elements_per_group : + total_number_of_elements ) ), + group_allocator_pair.max_elements_per_group ); + + if COLONY_CONSTEXPR( std::is_move_assignable::value && + std::is_move_constructible::value ) { + temp.insert( std::make_move_iterator( begin_iterator ), std::make_move_iterator( end_iterator ) ); + } else { + temp.insert( begin_iterator, end_iterator ); + } + // reset to correct value for future clear() or erasures + temp.pointer_allocator_pair.min_elements_per_group = pointer_allocator_pair.min_elements_per_group; + // Avoid generating 2nd temporary + *this = std::move( temp ); + } + + void remove_from_groups_with_erasures_list( const group_pointer_type group_to_remove ) noexcept { + if( group_to_remove == groups_with_erasures_list_head ) { + groups_with_erasures_list_head = groups_with_erasures_list_head->erasures_list_next_group; + return; + } + + group_pointer_type previous_group = groups_with_erasures_list_head; + group_pointer_type current_group = groups_with_erasures_list_head->erasures_list_next_group; + + while( group_to_remove != current_group ) { + previous_group = current_group; + current_group = current_group->erasures_list_next_group; + } + + previous_group->erasures_list_next_group = current_group->erasures_list_next_group; + } + + public: + + /** + * Removes the element pointed to by the supplied iterator, from the colony. Returns an + * iterator pointing to the next non-erased element in the colony (or to end() if no more + * elements are available). This must return an iterator because if a colony group becomes + * entirely empty, it will be removed from the colony, invalidating the existing iterator. + * Attempting to erase a previously-erased element results in undefined behavior (this is + * checked for via an assert in debug mode). + * + * must return iterator to subsequent non-erased element (or end()), in case the group + * containing the element which the iterator points to becomes empty after the erasure, and + * is thereafter removed from the colony chain, making the current iterator invalid and + * unusable in a ++ operation: + * + * if uninitialized/invalid iterator supplied, function could generate an exception + */ + iterator erase( const const_iterator &it ) { + assert( !empty() ); + const group_pointer_type group_pointer = it.group_pointer; + // not uninitialized iterator + assert( group_pointer != nullptr ); + // != end() + assert( it.element_pointer != group_pointer->last_endpoint ); + // element pointed to by iterator has not been erased previously + assert( *( it.skipfield_pointer ) == 0 ); + + // This if-statement should be removed by the compiler on resolution of element_type. For some optimizing compilers this step won't be necessary (for MSVC 2013 it makes a difference) + if COLONY_CONSTEXPR( !( std::is_trivially_destructible::value ) ) { + COLONY_DESTROY( element_allocator_type, ( *this ), + reinterpret_cast( it.element_pointer ) ); // Destruct element + } + + --total_number_of_elements; + + // ie. non-empty group at this point in time, don't consolidate - optimization note: GCC optimizes postfix + 1 comparison better than prefix + 1 comparison in many cases. + if( group_pointer->number_of_elements-- != 1 ) { + // Code logic for following section: + // --------------------------------- + // If current skipfield node has no skipped node on either side, continue as usual + // If node only has skipped node on left, set current node and start node of the skipblock to left node value + 1. + // If node only has skipped node on right, make this node the start node of the skipblock and update end node + // If node has skipped nodes on left and right, set start node of left skipblock and end node of right skipblock to the values of the left + right nodes + 1 + + // Optimization explanation: + // The contextual logic below is the same as that in the insert() functions but in this case the value of the current skipfield node will always be + // zero (since it is not yet erased), meaning no additional manipulations are necessary for the previous skipfield node comparison - we only have to check against zero + const char prev_skipfield = *( it.skipfield_pointer - ( it.skipfield_pointer != + group_pointer->skipfield ) ) != 0; + // NOTE: boundary test (checking against end-of-elements) is able to be skipped due to the extra skipfield node (compared to element field) - which is present to enable faster iterator operator ++ operations + const char after_skipfield = *( it.skipfield_pointer + 1 ) != 0; + skipfield_type update_value = 1; + + switch( ( after_skipfield << 1 ) | prev_skipfield ) { + case 0: { // no consecutive erased elements + *it.skipfield_pointer = 1; // solo skipped node + const skipfield_type index = static_cast( it.element_pointer - + group_pointer->elements ); + + // ie. if this group already has some erased elements + if( group_pointer->free_list_head != std::numeric_limits::max() ) { + // set prev free list head's 'next index' number to the index of the current element + *( reinterpret_cast( group_pointer->elements + + group_pointer->free_list_head ) + 1 ) = index; + } else { + // add it to the groups-with-erasures free list + group_pointer->erasures_list_next_group = groups_with_erasures_list_head; + groups_with_erasures_list_head = group_pointer; + } + + *( reinterpret_cast( it.element_pointer ) ) = group_pointer->free_list_head; + *( reinterpret_cast( it.element_pointer ) + 1 ) = + std::numeric_limits::max(); + group_pointer->free_list_head = index; + break; + } + case 1: { // previous erased consecutive elements, none following + *( it.skipfield_pointer - * ( it.skipfield_pointer - 1 ) ) = *it.skipfield_pointer = * + ( it.skipfield_pointer - 1 ) + 1; + break; + } + case 2: { // following erased consecutive elements, none preceding + const skipfield_type following_value = *( it.skipfield_pointer + 1 ) + 1; + *( it.skipfield_pointer + following_value - 1 ) = *( it.skipfield_pointer ) = following_value; + + const skipfield_type following_previous = *( reinterpret_cast + ( it.element_pointer + 1 ) ); + const skipfield_type following_next = *( reinterpret_cast + ( it.element_pointer + 1 ) + 1 ); + *( reinterpret_cast( it.element_pointer ) ) = following_previous; + *( reinterpret_cast( it.element_pointer ) + 1 ) = following_next; + + const skipfield_type index = static_cast( it.element_pointer - + group_pointer->elements ); + + if( following_previous != std::numeric_limits::max() ) { + // Set next index of previous free list node to this node's 'next' index + *( reinterpret_cast( group_pointer->elements + following_previous ) + 1 ) = + index; + } + + if( following_next != std::numeric_limits::max() ) { + // Set previous index of next free list node to this node's 'previous' index + *( reinterpret_cast( group_pointer->elements + following_next ) ) = index; + } else { + group_pointer->free_list_head = index; + } + + update_value = following_value; + break; + } + case 3: { // both preceding and following consecutive erased elements + const skipfield_type preceding_value = *( it.skipfield_pointer - 1 ); + const skipfield_type following_value = *( it.skipfield_pointer + 1 ) + 1; + + // Join the skipblocks + *( it.skipfield_pointer - preceding_value ) = *( it.skipfield_pointer + following_value - 1 ) = + preceding_value + following_value; + + // Remove the following skipblock's entry from the free list + const skipfield_type following_previous = *( reinterpret_cast + ( it.element_pointer + 1 ) ); + const skipfield_type following_next = *( reinterpret_cast + ( it.element_pointer + 1 ) + 1 ); + + if( following_previous != std::numeric_limits::max() ) { + // Set next index of previous free list node to this node's 'next' index + *( reinterpret_cast( group_pointer->elements + following_previous ) + 1 ) = + following_next; + } + + if( following_next != std::numeric_limits::max() ) { + // Set previous index of next free list node to this node's 'previous' index + *( reinterpret_cast( group_pointer->elements + following_next ) ) = + following_previous; + } else { + group_pointer->free_list_head = following_previous; + } + + update_value = following_value; + break; + } + } + + iterator return_iterator( it.group_pointer, it.element_pointer + update_value, + it.skipfield_pointer + update_value ); + return_iterator.check_for_end_of_group_and_progress(); + + // If original iterator was first element in colony, update it's value with the next non-erased element: + if( it.element_pointer == begin_iterator.element_pointer ) { + begin_iterator = return_iterator; + } + + return return_iterator; + } + + // else: group is empty, consolidate groups + switch( ( group_pointer->next_group != nullptr ) | ( ( group_pointer != + begin_iterator.group_pointer ) + << 1 ) ) { + case 0: { // ie. group_pointer == begin_iterator.group_pointer && group_pointer->next_group == NULL; only group in colony + // Reset skipfield and free list rather than clearing - leads to fewer allocations/deallocations: + // &* to avoid problems with non-trivial pointers. Although there is one more skipfield than group_pointer->capacity, capacity + 1 is not necessary here as the end skipfield is never written to after initialization + std::memset( &*( group_pointer->skipfield ), 0, + sizeof( skipfield_type ) * group_pointer->capacity ); + group_pointer->free_list_head = std::numeric_limits::max(); + groups_with_erasures_list_head = nullptr; + + // Reset begin and end iterators: + end_iterator.element_pointer = begin_iterator.element_pointer = group_pointer->last_endpoint = + group_pointer->elements; + end_iterator.skipfield_pointer = begin_iterator.skipfield_pointer = group_pointer->skipfield; + + return end_iterator; + } + case 1: { // ie. group_pointer == begin_iterator.group_pointer && group_pointer->next_group != NULL. Remove first group, change first group to next group + group_pointer->next_group->previous_group = nullptr; // Cut off this group from the chain + begin_iterator.group_pointer = group_pointer->next_group; // Make the next group the first group + + update_subsequent_group_numbers( begin_iterator.group_pointer ); + + // Erasures present within the group, ie. was part of the intrusive list of groups with erasures. + if( group_pointer->free_list_head != std::numeric_limits::max() ) { + remove_from_groups_with_erasures_list( group_pointer ); + } + + total_capacity -= group_pointer->capacity; + COLONY_DESTROY( group_allocator_type, group_allocator_pair, group_pointer ); + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, group_pointer, 1 ); + + // note: end iterator only needs to be changed if the deleted group was the final group in the chain ie. not in this case + // If the beginning index has been erased (ie. skipfield != 0), skip to next non-erased element + begin_iterator.element_pointer = begin_iterator.group_pointer->elements + * + ( begin_iterator.group_pointer->skipfield ); + begin_iterator.skipfield_pointer = begin_iterator.group_pointer->skipfield + * + ( begin_iterator.group_pointer->skipfield ); + + return begin_iterator; + } + case 3: { // this is a non-first group but not final group in chain: delete the group, then link previous group to the next group in the chain: + group_pointer->next_group->previous_group = group_pointer->previous_group; + // close the chain, removing this group from it + const group_pointer_type return_group = group_pointer->previous_group->next_group = + group_pointer->next_group; + + update_subsequent_group_numbers( return_group ); + + if( group_pointer->free_list_head != std::numeric_limits::max() ) { + remove_from_groups_with_erasures_list( group_pointer ); + } + + total_capacity -= group_pointer->capacity; + COLONY_DESTROY( group_allocator_type, group_allocator_pair, group_pointer ); + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, group_pointer, 1 ); + + // Return next group's first non-erased element: + return iterator( return_group, return_group->elements + * ( return_group->skipfield ), + return_group->skipfield + * ( return_group->skipfield ) ); + } + default: { // this is a non-first group and the final group in the chain + if( group_pointer->free_list_head != std::numeric_limits::max() ) { + remove_from_groups_with_erasures_list( group_pointer ); + } + + group_pointer->previous_group->next_group = nullptr; + // end iterator needs to be changed as element supplied was the back element of the colony + end_iterator.group_pointer = group_pointer->previous_group; + end_iterator.element_pointer = reinterpret_cast + ( end_iterator.group_pointer->skipfield ); + end_iterator.skipfield_pointer = end_iterator.group_pointer->skipfield + + end_iterator.group_pointer->capacity; + + total_capacity -= group_pointer->capacity; + COLONY_DESTROY( group_allocator_type, group_allocator_pair, group_pointer ); + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, group_pointer, 1 ); + + return end_iterator; + } + } + } + + /** + * Range erase: + * Erases all elements of a given colony from first to the element before the last iterator. + * This function is optimized for multiple consecutive erasures and will always be faster + * than sequential single-element erase calls in that scenario. + */ + void erase( const const_iterator &iterator1, const const_iterator &iterator2 ) { + // if uninitialized/invalid iterators supplied, function could generate an exception. If iterator1 > iterator2, behavior is undefined. + assert( iterator1 <= iterator2 ); + + iterator current = iterator1; + + if( current.group_pointer != iterator2.group_pointer ) { + if( current.element_pointer != current.group_pointer->elements + * + ( current.group_pointer->skipfield ) ) { + // if iterator1 is not the first non-erased element in it's group - most common case + size_type number_of_group_erasures = 0; + + // Now update skipfield: + const aligned_pointer_type end = iterator1.group_pointer->last_endpoint; + + // Schema: first erase all non-erased elements until end of group & remove all skipblocks post-iterator1 from the free_list. Then, either update preceding skipblock or create new one: + + // if trivially-destructible, and no erasures in group, skip while loop below and just jump straight to the location + if( ( std::is_trivially_destructible::value ) & + ( current.group_pointer->free_list_head == std::numeric_limits::max() ) ) { + number_of_group_erasures += static_cast( end - current.element_pointer ); + } else { + while( current.element_pointer != end ) { + if( *current.skipfield_pointer == 0 ) { + if COLONY_CONSTEXPR( !( std::is_trivially_destructible::value ) ) { + COLONY_DESTROY( element_allocator_type, ( *this ), + reinterpret_cast( current.element_pointer ) ); // Destruct element + } + + ++number_of_group_erasures; + ++current.element_pointer; + ++current.skipfield_pointer; + } else { // remove skipblock from group: + const skipfield_type prev_free_list_index = *( reinterpret_cast + ( current.element_pointer ) ); + const skipfield_type next_free_list_index = *( reinterpret_cast + ( current.element_pointer ) + 1 ); + + current.element_pointer += *( current.skipfield_pointer ); + current.skipfield_pointer += *( current.skipfield_pointer ); + + // if this is the last skipblock in the free list + if( next_free_list_index == std::numeric_limits::max() && + prev_free_list_index == std::numeric_limits::max() ) { + // remove group from list of free-list groups - will be added back in down below, but not worth optimizing for + remove_from_groups_with_erasures_list( iterator1.group_pointer ); + iterator1.group_pointer->free_list_head = std::numeric_limits::max(); + number_of_group_erasures += static_cast( end - current.element_pointer ); + + if COLONY_CONSTEXPR( !( std::is_trivially_destructible::value ) ) { + // miniloop - avoid checking skipfield for rest of elements in group, as there are no more skipped elements now + while( current.element_pointer != end ) { + COLONY_DESTROY( element_allocator_type, ( *this ), + reinterpret_cast( current.element_pointer++ ) ); // Destruct element + } + } + + break; // end overall while loop + } else if( next_free_list_index == std::numeric_limits::max() ) { + // if this is the head of the free list + // make free list head equal to next free list node + current.group_pointer->free_list_head = prev_free_list_index; + *( reinterpret_cast( current.group_pointer->elements + + prev_free_list_index ) + 1 ) = std::numeric_limits::max(); + } else { // either a tail or middle free list node + *( reinterpret_cast( current.group_pointer->elements + + next_free_list_index ) ) = prev_free_list_index; + + if( prev_free_list_index != + std::numeric_limits::max() ) { // ie. not the tail free list node + *( reinterpret_cast( current.group_pointer->elements + + prev_free_list_index ) + 1 ) = next_free_list_index; + } + } + } + } + } + + const skipfield_type previous_node_value = *( iterator1.skipfield_pointer - 1 ); + const skipfield_type distance_to_end = static_cast( end - + iterator1.element_pointer ); + + if( previous_node_value == 0 ) { // no previous skipblock + *iterator1.skipfield_pointer = distance_to_end; + *( iterator1.skipfield_pointer + distance_to_end - 1 ) = distance_to_end; + + const skipfield_type index = static_cast( iterator1.element_pointer - + iterator1.group_pointer->elements ); + + if( iterator1.group_pointer->free_list_head != std::numeric_limits::max() ) { + // if this group already has some erased elements + // set prev free list head's 'next index' number to the index of the iterator1 element + *( reinterpret_cast( iterator1.group_pointer->elements + + iterator1.group_pointer->free_list_head ) + 1 ) = index; + } else { + // add it to the groups-with-erasures free list + iterator1.group_pointer->erasures_list_next_group = groups_with_erasures_list_head; + groups_with_erasures_list_head = iterator1.group_pointer; + } + + *( reinterpret_cast( iterator1.element_pointer ) ) = + iterator1.group_pointer->free_list_head; + *( reinterpret_cast( iterator1.element_pointer ) + 1 ) = + std::numeric_limits::max(); + iterator1.group_pointer->free_list_head = index; + } else { + // update previous skipblock, no need to update free list: + *( iterator1.skipfield_pointer - previous_node_value ) = *( iterator1.skipfield_pointer + + distance_to_end - 1 ) = previous_node_value + distance_to_end; + } + + iterator1.group_pointer->number_of_elements -= static_cast + ( number_of_group_erasures ); + total_number_of_elements -= number_of_group_erasures; + + current.group_pointer = current.group_pointer->next_group; + } + + // Intermediate groups: + const group_pointer_type previous_group = current.group_pointer->previous_group; + + while( current.group_pointer != iterator2.group_pointer ) { + if COLONY_CONSTEXPR( !( std::is_trivially_destructible::value ) ) { + current.element_pointer = current.group_pointer->elements + *( current.group_pointer->skipfield ); + current.skipfield_pointer = current.group_pointer->skipfield + * + ( current.group_pointer->skipfield ); + const aligned_pointer_type end = current.group_pointer->last_endpoint; + + do { + COLONY_DESTROY( element_allocator_type, ( *this ), + reinterpret_cast( current.element_pointer ) ); // Destruct element + ++current.skipfield_pointer; + current.element_pointer += *current.skipfield_pointer + 1; + current.skipfield_pointer += *current.skipfield_pointer; + } while( current.element_pointer != end ); + } + + if( current.group_pointer->free_list_head != std::numeric_limits::max() ) { + remove_from_groups_with_erasures_list( current.group_pointer ); + } + + total_number_of_elements -= current.group_pointer->number_of_elements; + const group_pointer_type current_group = current.group_pointer; + current.group_pointer = current.group_pointer->next_group; + + total_capacity -= current_group->capacity; + COLONY_DESTROY( group_allocator_type, group_allocator_pair, current_group ); + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, current_group, 1 ); + } + + current.element_pointer = current.group_pointer->elements + *( current.group_pointer->skipfield ); + current.skipfield_pointer = current.group_pointer->skipfield + * + ( current.group_pointer->skipfield ); + current.group_pointer->previous_group = previous_group; + + if( previous_group != nullptr ) { + previous_group->next_group = current.group_pointer; + } else { + // This line is included here primarily to avoid a secondary if statement within the if block below - it will not be needed in any other situation + begin_iterator = iterator2; + } + } + + // in case iterator2 was at beginning of it's group - also covers empty range case (first == last) + if( current.element_pointer == iterator2.element_pointer ) { + return; + } + + // Final group: + // Code explanation: + // If not erasing entire final group, 1. Destruct elements (if non-trivial destructor) and add locations to group free list. 2. process skipfield. + // If erasing entire group, 1. Destruct elements (if non-trivial destructor), 2. if no elements left in colony, clear() 3. otherwise reset end_iterator and remove group from groups-with-erasures list (if free list of erasures present) + if( iterator2.element_pointer != end_iterator.element_pointer || + current.element_pointer != current.group_pointer->elements + * + ( current.group_pointer->skipfield ) ) { // ie. not erasing entire group + size_type number_of_group_erasures = 0; + // Schema: first erased all non-erased elements until end of group & remove all skipblocks post-iterator2 from the free_list. Then, either update preceding skipblock or create new one: + + const iterator current_saved = current; + + // if trivially-destructible, and no erasures in group, skip while loop below and just jump straight to the location + if( ( std::is_trivially_destructible::value ) & + ( current.group_pointer->free_list_head == std::numeric_limits::max() ) ) { + number_of_group_erasures += static_cast( iterator2.element_pointer - + current.element_pointer ); + } else { + while( current.element_pointer != iterator2.element_pointer ) { + if( *current.skipfield_pointer == 0 ) { + if COLONY_CONSTEXPR( !( std::is_trivially_destructible::value ) ) { + COLONY_DESTROY( element_allocator_type, ( *this ), + reinterpret_cast( current.element_pointer ) ); // Destruct element + } + + ++number_of_group_erasures; + ++current.element_pointer; + ++current.skipfield_pointer; + } else { // remove skipblock from group: + const skipfield_type prev_free_list_index = *( reinterpret_cast + ( current.element_pointer ) ); + const skipfield_type next_free_list_index = *( reinterpret_cast + ( current.element_pointer ) + 1 ); + + current.element_pointer += *( current.skipfield_pointer ); + current.skipfield_pointer += *( current.skipfield_pointer ); + + if( next_free_list_index == std::numeric_limits::max() && + prev_free_list_index == std::numeric_limits::max() ) { + // if this is the last skipblock in the free list + // remove group from list of free-list groups - will be added back in down below, but not worth optimizing for + remove_from_groups_with_erasures_list( iterator2.group_pointer ); + iterator2.group_pointer->free_list_head = std::numeric_limits::max(); + number_of_group_erasures += static_cast( iterator2.element_pointer - + current.element_pointer ); + + if COLONY_CONSTEXPR( !( std::is_trivially_destructible::value ) ) { + while( current.element_pointer != iterator2.element_pointer ) { + COLONY_DESTROY( element_allocator_type, ( *this ), + reinterpret_cast( current.element_pointer++ ) ); // Destruct element + } + } + + break; // end overall while loop + } else if( next_free_list_index == + std::numeric_limits::max() ) { // if this is the head of the free list + current.group_pointer->free_list_head = prev_free_list_index; + *( reinterpret_cast( current.group_pointer->elements + + prev_free_list_index ) + 1 ) = std::numeric_limits::max(); + } else { + *( reinterpret_cast( current.group_pointer->elements + + next_free_list_index ) ) = prev_free_list_index; + + if( prev_free_list_index != + std::numeric_limits::max() ) { // ie. not the tail free list node + *( reinterpret_cast( current.group_pointer->elements + + prev_free_list_index ) + 1 ) = next_free_list_index; + } + } + } + } + } + + const skipfield_type distance_to_iterator2 = static_cast + ( iterator2.element_pointer - current_saved.element_pointer ); + const skipfield_type index = static_cast( current_saved.element_pointer - + iterator2.group_pointer->elements ); + + if( index == 0 || *( current_saved.skipfield_pointer - 1 ) == 0 ) { + // element is either at start of group or previous skipfield node is 0 + *( current_saved.skipfield_pointer ) = distance_to_iterator2; + *( iterator2.skipfield_pointer - 1 ) = distance_to_iterator2; + + if( iterator2.group_pointer->free_list_head != std::numeric_limits::max() ) { + // if this group already has some erased elements + *( reinterpret_cast( iterator2.group_pointer->elements + + iterator2.group_pointer->free_list_head ) + 1 ) = index; + } else { + // add it to the groups-with-erasures free list + iterator2.group_pointer->erasures_list_next_group = groups_with_erasures_list_head; + groups_with_erasures_list_head = iterator2.group_pointer; + } + + *( reinterpret_cast( current_saved.element_pointer ) ) = + iterator2.group_pointer->free_list_head; + *( reinterpret_cast( current_saved.element_pointer ) + 1 ) = + std::numeric_limits::max(); + iterator2.group_pointer->free_list_head = index; + } else { // If iterator 1 & 2 are in same group, but iterator 1 was not at start of group, and previous skipfield node is an end node in a skipblock: + // Just update existing skipblock, no need to create new free list node: + const skipfield_type prev_node_value = *( current_saved.skipfield_pointer - 1 ); + *( current_saved.skipfield_pointer - prev_node_value ) = prev_node_value + distance_to_iterator2; + *( iterator2.skipfield_pointer - 1 ) = prev_node_value + distance_to_iterator2; + } + + if( iterator1.element_pointer == begin_iterator.element_pointer ) { + begin_iterator = iterator2; + } + + iterator2.group_pointer->number_of_elements -= static_cast + ( number_of_group_erasures ); + total_number_of_elements -= number_of_group_erasures; + } else { // ie. full group erasure + if COLONY_CONSTEXPR( !( std::is_trivially_destructible::value ) ) { + while( current.element_pointer != iterator2.element_pointer ) { + COLONY_DESTROY( element_allocator_type, ( *this ), + reinterpret_cast( current.element_pointer ) ); + ++current.skipfield_pointer; + current.element_pointer += 1 + *current.skipfield_pointer; + current.skipfield_pointer += *current.skipfield_pointer; + } + } + + if( ( total_number_of_elements -= current.group_pointer->number_of_elements ) != 0 ) { + // ie. previous_group != NULL + current.group_pointer->previous_group->next_group = current.group_pointer->next_group; + end_iterator.group_pointer = current.group_pointer->previous_group; + end_iterator.element_pointer = current.group_pointer->previous_group->last_endpoint; + end_iterator.skipfield_pointer = current.group_pointer->previous_group->skipfield + + current.group_pointer->previous_group->capacity; + total_capacity -= current.group_pointer->capacity; + + if( current.group_pointer->free_list_head != std::numeric_limits::max() ) { + remove_from_groups_with_erasures_list( current.group_pointer ); + } + } else { // ie. colony is now empty + blank(); + } + + COLONY_DESTROY( group_allocator_type, group_allocator_pair, current.group_pointer ); + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, current.group_pointer, 1 ); + } + } + + /** Returns a boolean indicating whether the colony is currently empty of elements. */ + inline COLONY_FORCE_INLINE bool empty() const noexcept { + return total_number_of_elements == 0; + } + + /** Returns total number of elements currently stored in container. */ + inline size_type size() const noexcept { + return total_number_of_elements; + } + + /** Returns the maximum number of elements that the allocator can store in the container. */ + inline size_type max_size() const noexcept { + return std::allocator_traits::max_size( *this ); + } + + /** Returns total number of elements currently able to be stored in container without expansion. */ + inline size_type capacity() const noexcept { + return total_capacity; + } + + /** + * Returns the approximate memory use of the container plus it's elements. Will be + * inaccurate if the elements themselves dynamically-allocate data. Utility function + * primarily for benchmarking. + */ + inline size_type approximate_memory_use() const noexcept { + return + sizeof( *this ) + // sizeof colony basic structure + ( total_capacity * ( sizeof( aligned_element_type ) + sizeof( skipfield_type ) ) ) + + // sizeof current colony data capacity + skipfields + ( ( end_iterator.group_pointer == NULL ) ? 0 : ( ( end_iterator.group_pointer->group_number + 1 ) + * ( sizeof( group ) + sizeof( + skipfield_type ) ) ) ); // if colony not empty, add the memory usage of the group structures themselves, adding the extra skipfield node + } + + /** + * Changes the minimum and maximum internal group sizes, in terms of number of elements + * stored per group. If the colony is not empty and either min_group_size is larger than the + * smallest group in the colony, or max_group_size is smaller than the largest group in the + * colony, the colony will be internally copy-constructed into a new colony which uses the + * new group sizes, invalidating all pointers/iterators/references. If trying to change + * group sizes with a colony storing a non-copyable/movable type, please use the + * reinitialize function instead. + */ + void change_group_sizes( const skipfield_type min_allocation_amount, + const skipfield_type max_allocation_amount ) { + assert( ( min_allocation_amount > 2 ) & ( min_allocation_amount <= max_allocation_amount ) ); + + pointer_allocator_pair.min_elements_per_group = min_allocation_amount; + group_allocator_pair.max_elements_per_group = max_allocation_amount; + + if( begin_iterator.group_pointer != nullptr && + ( begin_iterator.group_pointer->capacity < min_allocation_amount || + end_iterator.group_pointer->capacity > max_allocation_amount ) ) { + consolidate(); + } + } + + /** + * Changes the minimum internal group size only, in terms of minimum number of elements + * stored per group. If the colony is not empty and min_group_size is larger than the + * smallest group in the colony, the colony will be internally move-constructed (if possible) + * or copy-constructed into a new colony which uses the new minimum group size, invalidating + * all pointers/iterators/references. If trying to change group sizes with a colony storing + * a non-copyable/movable type, please use the reinitialize function instead. + */ + inline void change_minimum_group_size( const skipfield_type min_allocation_amount ) { + change_group_sizes( min_allocation_amount, group_allocator_pair.max_elements_per_group ); + } + + /** + * Changes the maximum internal group size only, in terms of maximum number of elements + * stored per group. If the colony is not empty and either max_group_size is smaller than + * the largest group in the colony, the colony will be internally move-constructed (if + * possible) or copy-constructed into a new colony which uses the new maximum group size, + * invalidating all pointers/iterators/references. If trying to change group sizes with a + * colony storing a non-copyable/movable type, please use the reinitialize function instead. + */ + inline void change_maximum_group_size( const skipfield_type max_allocation_amount ) { + change_group_sizes( pointer_allocator_pair.min_elements_per_group, max_allocation_amount ); + } + + /** + * Sets @param minimum_group_size and @param maximum_group_size to the minimum and maximum + * group size respectively + */ + inline void get_group_sizes( skipfield_type &minimum_group_size, + skipfield_type &maximum_group_size ) const noexcept { + minimum_group_size = pointer_allocator_pair.min_elements_per_group; + maximum_group_size = group_allocator_pair.max_elements_per_group; + } + + /** + * Semantics of this function are the same as + * clear(); + * change_group_sizes(min_group_size, max_group_size); + * but without the move/copy-construction code of the change_group_sizes() function - this + * means it can be used with element types which are non-copy-constructible and + * non-move-constructible, unlike change_group_sizes(). + */ + inline void reinitialize( const skipfield_type min_allocation_amount, + const skipfield_type max_allocation_amount ) noexcept { + assert( ( min_allocation_amount > 2 ) & ( min_allocation_amount <= max_allocation_amount ) ); + clear(); + pointer_allocator_pair.min_elements_per_group = min_allocation_amount; + group_allocator_pair.max_elements_per_group = max_allocation_amount; + } + + /** + * Empties the colony and removes all elements and groups. + */ + inline COLONY_FORCE_INLINE void clear() noexcept { + destroy_all_data(); + blank(); + } + + /** + * Copy assignment: + * Copy the elements from another colony to this colony, clearing this colony of existing + * elements first. + */ + inline colony &operator=( const colony &source ) { + assert( &source != this ); + + destroy_all_data(); + colony temp( source ); + // Avoid generating 2nd temporary + *this = std::move( temp ); + + return *this; + } + + /** + * Move assignment: + * Move the elements from another colony to this colony, clearing this colony of existing + * elements first. Source colony is now empty and in a valid state (same as a new colony + * without any insertions), can be safely destructed or used in any regular way without + * problems. + */ + colony &operator=( colony &&source ) COLONY_NOEXCEPT_MOVE_ASSIGNMENT( allocator_type ) { + assert( &source != this ); + destroy_all_data(); + + if COLONY_CONSTEXPR( std::is_trivial::value && + std::is_trivial::value && std::is_trivial::value ) { + // NOLINTNEXTLINE(bugprone-undefined-memory-manipulation) + std::memcpy( static_cast( this ), &source, sizeof( colony ) ); + } else { + end_iterator = std::move( source.end_iterator ); + begin_iterator = std::move( source.begin_iterator ); + groups_with_erasures_list_head = source.groups_with_erasures_list_head; + total_number_of_elements = source.total_number_of_elements; + total_capacity = source.total_capacity; + pointer_allocator_pair.min_elements_per_group = + source.pointer_allocator_pair.min_elements_per_group; + group_allocator_pair.max_elements_per_group = source.group_allocator_pair.max_elements_per_group; + } + + source.blank(); + return *this; + } + + /** Compare contents of another colony to this colony. Returns true if they are equal. */ + bool operator==( const colony &rh ) const noexcept { + assert( this != &rh ); + + if( total_number_of_elements != rh.total_number_of_elements ) { + return false; + } + + for( iterator lh_iterator = begin_iterator, rh_iterator = rh.begin_iterator; + lh_iterator != end_iterator; ) { + if( *rh_iterator++ != *lh_iterator++ ) { + return false; + } + } + + return true; + } + + /** Compare contents of another colony to this colony. Returns true if they are unequal. */ + inline bool operator!=( const colony &rh ) const noexcept { + return !( *this == rh ); + } + + /** + * Reduces container capacity to the amount necessary to store all currently stored + * elements, consolidates elements and removes any erased locations. If the total number of + * elements is larger than the maximum group size, the resultant capacity will be equal to + * ((total_elements / max_group_size) + 1) * max_group_size (rounding down at division). + * Invalidates all pointers, iterators and references to elements within the container. + */ + void shrink_to_fit() { + if( total_number_of_elements == total_capacity ) { + return; + } else if( total_number_of_elements == 0 ) { // Edge case + clear(); + return; + } + + consolidate(); + } + + /** + * Preallocates memory space sufficient to store the number of elements indicated by + * reserve_amount. The maximum size for this number is currently limited to the maximum + * group size of the colony and will be rounded down if necessary. The default maximum group + * size is 65535 on the majority of platforms. The default minimum reserve amount is the + * same as the current minimum group size, and will be rounded up silently if necessary. + * This function is useful from a performance perspective when the user is inserting + * elements singly, but the overall number of insertions is known in advance. By reserving, + * colony can forgo creating many smaller memory block allocations (due to colony's growth + * factor) and reserve a single memory block instead. Alternatively one could simply change + * the default group sizes. + */ + void reserve( const size_type original_reserve_amount ) { + if( original_reserve_amount == 0 || original_reserve_amount <= total_capacity ) { + // Already have enough space allocated + return; + } + + skipfield_type reserve_amount; + + if( original_reserve_amount > static_cast + ( group_allocator_pair.max_elements_per_group ) ) { + reserve_amount = group_allocator_pair.max_elements_per_group; + } else if( original_reserve_amount < static_cast + ( pointer_allocator_pair.min_elements_per_group ) ) { + reserve_amount = pointer_allocator_pair.min_elements_per_group; + } else if( original_reserve_amount > max_size() ) { + reserve_amount = static_cast( max_size() ); + } else { + reserve_amount = static_cast( original_reserve_amount ); + } + + if( total_number_of_elements == 0 ) { // Most common scenario - empty colony + if( begin_iterator.group_pointer != nullptr ) { + // Edge case - empty colony but first group is initialized ie. had some insertions but all elements got subsequently erased + COLONY_DESTROY( group_allocator_type, group_allocator_pair, begin_iterator.group_pointer ); + COLONY_DEALLOCATE( group_allocator_type, group_allocator_pair, begin_iterator.group_pointer, 1 ); + } // else: Empty colony, no insertions yet, time to allocate + + initialize( reserve_amount ); + // last_endpoint initially == elements + 1 via default constructor + begin_iterator.group_pointer->last_endpoint = begin_iterator.group_pointer->elements; + begin_iterator.group_pointer->number_of_elements = 0; // 1 by default + } else { // Non-empty colony, don't have enough space allocated + const skipfield_type original_min_elements = pointer_allocator_pair.min_elements_per_group; + // Make sure all groups are at maximum appropriate capacity (this amount already rounded down to a skipfield type earlier in function) + pointer_allocator_pair.min_elements_per_group = static_cast( reserve_amount ); + consolidate(); + pointer_allocator_pair.min_elements_per_group = original_min_elements; + } + } + + /** + * Increments/decrements the iterator supplied by the positive or negative amount indicated + * by distance. Speed of incrementation will almost always be faster than using the ++ + * operator on the iterator for increments greater than 1. In some cases it may approximate + * O(1). The iterator_type can be an iterator, const_iterator, reverse_iterator or + * const_reverse_iterator. + */ + // Advance implementation for iterator and const_iterator: + // Cannot be noexcept due to the possibility of an uninitialized iterator + template + void advance( colony_iterator &it, difference_type distance ) const { + // For code simplicity - should hopefully be optimized out by compiler: + group_pointer_type &group_pointer = it.group_pointer; + aligned_pointer_type &element_pointer = it.element_pointer; + skipfield_pointer_type &skipfield_pointer = it.skipfield_pointer; + + // covers uninitialized colony_iterator && empty group + assert( group_pointer != nullptr ); + + // Now, run code based on the nature of the distance type - negative, positive or zero: + if( distance > 0 ) { // ie. += + // Code explanation: + // For the initial state of the iterator, we don't know how what elements have been erased before that element in that group. + // So for the first group, we follow the following logic: + // 1. If no elements have been erased in the group, we do simple addition to progress either to within the group (if the distance is small enough) or the end of the group and subtract from distance accordingly. + // 2. If any of the first group elements have been erased, we manually iterate, as we don't know whether the erased elements occur before or after the initial iterator position, and we subtract 1 from the distance amount each time. Iteration continues until either distance becomes zero, or we reach the end of the group. + + // For all subsequent groups, we follow this logic: + // 1. If distance is larger than the total number of non-erased elements in a group, we skip that group and subtract the number of elements in that group from distance + // 2. If distance is smaller than the total number of non-erased elements in a group, then: + // a. if there're no erased elements in the group we simply add distance to group->elements to find the new location for the iterator + // b. if there are erased elements in the group, we manually iterate and subtract 1 from distance on each iteration, until the new iterator location is found ie. distance = 0 + + // Note: incrementing element_pointer is avoided until necessary to avoid needless calculations + + // Check that we're not already at end() + assert( !( element_pointer == group_pointer->last_endpoint && + group_pointer->next_group == nullptr ) ); + + // Special case for initial element pointer and initial group (we don't know how far into the group the element pointer is) + if( element_pointer != group_pointer->elements + * ( group_pointer->skipfield ) ) { + // ie. != first non-erased element in group + const difference_type distance_from_end = static_cast + ( group_pointer->last_endpoint - element_pointer ); + + if( group_pointer->number_of_elements == static_cast( distance_from_end ) ) { + // ie. if there are no erasures in the group (using endpoint - elements_start to determine number of elements in group just in case this is the last group of the colony, in which case group->last_endpoint != group->elements + group->capacity) + if( distance < distance_from_end ) { + element_pointer += distance; + skipfield_pointer += distance; + return; + } else if( group_pointer->next_group == nullptr ) { + // either we've reached end() or gone beyond it, so bound to end() + element_pointer = group_pointer->last_endpoint; + skipfield_pointer += distance_from_end; + return; + } else { + distance -= distance_from_end; + } + } else { + const skipfield_pointer_type endpoint = skipfield_pointer + distance_from_end; + + while( true ) { + ++skipfield_pointer; + skipfield_pointer += *skipfield_pointer; + --distance; + + if( skipfield_pointer == endpoint ) { + break; + } else if( distance == 0 ) { + element_pointer = group_pointer->elements + ( skipfield_pointer - group_pointer->skipfield ); + return; + } + } + + if( group_pointer->next_group == nullptr ) { + // either we've reached end() or gone beyond it, so bound to end() + element_pointer = group_pointer->last_endpoint; + return; + } + } + + group_pointer = group_pointer->next_group; + + if( distance == 0 ) { + element_pointer = group_pointer->elements + *( group_pointer->skipfield ); + skipfield_pointer = group_pointer->skipfield + *( group_pointer->skipfield ); + return; + } + } + + // Intermediary groups - at the start of this code block and the subsequent block, the position of the iterator is assumed to be the first non-erased element in the current group: + while( static_cast( group_pointer->number_of_elements ) <= distance ) { + if( group_pointer->next_group == nullptr ) { + // either we've reached end() or gone beyond it, so bound to end() + element_pointer = group_pointer->last_endpoint; + skipfield_pointer = group_pointer->skipfield + ( group_pointer->last_endpoint - + group_pointer->elements ); + return; + } else if( ( distance -= group_pointer->number_of_elements ) == 0 ) { + group_pointer = group_pointer->next_group; + element_pointer = group_pointer->elements + *( group_pointer->skipfield ); + skipfield_pointer = group_pointer->skipfield + *( group_pointer->skipfield ); + return; + } else { + group_pointer = group_pointer->next_group; + } + } + + // Final group (if not already reached): + if( group_pointer->free_list_head == std::numeric_limits::max() ) { + // No erasures in this group, use straight pointer addition + element_pointer = group_pointer->elements + distance; + skipfield_pointer = group_pointer->skipfield + distance; + return; + } else { // ie. number_of_elements > distance - safe to ignore endpoint check condition while incrementing: + skipfield_pointer = group_pointer->skipfield + *( group_pointer->skipfield ); + + do { + ++skipfield_pointer; + skipfield_pointer += *skipfield_pointer; + } while( --distance != 0 ); + + element_pointer = group_pointer->elements + ( skipfield_pointer - group_pointer->skipfield ); + return; + } + + return; + } else if( distance < 0 ) { // for negative change + // Code logic is very similar to += above + + // check that we're not already at begin() + assert( !( ( element_pointer == group_pointer->elements + * ( group_pointer->skipfield ) ) && + group_pointer->previous_group == nullptr ) ); + distance = -distance; + + // Special case for initial element pointer and initial group (we don't know how far into the group the element pointer is) + if( element_pointer != group_pointer->last_endpoint ) { // ie. != end() + if( group_pointer->free_list_head == std::numeric_limits::max() ) { + // ie. no prior erasures have occurred in this group + const difference_type distance_from_beginning = static_cast + ( element_pointer - group_pointer->elements ); + + if( distance <= distance_from_beginning ) { + element_pointer -= distance; + skipfield_pointer -= distance; + return; + } else if( group_pointer->previous_group == nullptr ) { + // ie. we've gone before begin(), so bound to begin() + element_pointer = group_pointer->elements; + skipfield_pointer = group_pointer->skipfield; + return; + } else { + distance -= distance_from_beginning; + } + } else { + const skipfield_pointer_type beginning_point = group_pointer->skipfield + * + ( group_pointer->skipfield ); + + while( skipfield_pointer != beginning_point ) { + --skipfield_pointer; + skipfield_pointer -= *skipfield_pointer; + + if( --distance == 0 ) { + element_pointer = group_pointer->elements + ( skipfield_pointer - group_pointer->skipfield ); + return; + } + } + + if( group_pointer->previous_group == nullptr ) { + // This is first group, so bound to begin() (just in case final decrement took us before begin()) + element_pointer = group_pointer->elements + *( group_pointer->skipfield ); + skipfield_pointer = group_pointer->skipfield + *( group_pointer->skipfield ); + return; + } + } + + group_pointer = group_pointer->previous_group; + } + + // Intermediary groups - at the start of this code block and the subsequent block, the position of the iterator is assumed to be either the first non-erased element in the next group over, or end(): + while( static_cast( group_pointer->number_of_elements ) < distance ) { + if( group_pointer->previous_group == nullptr ) { + // we've gone beyond begin(), so bound to it + element_pointer = group_pointer->elements + *( group_pointer->skipfield ); + skipfield_pointer = group_pointer->skipfield + *( group_pointer->skipfield ); + return; + } + + distance -= group_pointer->number_of_elements; + group_pointer = group_pointer->previous_group; + } + + // Final group (if not already reached): + if( static_cast( group_pointer->number_of_elements ) == distance ) { + element_pointer = group_pointer->elements + *( group_pointer->skipfield ); + skipfield_pointer = group_pointer->skipfield + *( group_pointer->skipfield ); + return; + } else if( group_pointer->free_list_head == std::numeric_limits::max() ) { + // ie. no erased elements in this group + element_pointer = reinterpret_cast( group_pointer->skipfield ) - distance; + skipfield_pointer = ( group_pointer->skipfield + group_pointer->capacity ) - distance; + return; + } else { // ie. no more groups to traverse but there are erased elements in this group + skipfield_pointer = group_pointer->skipfield + group_pointer->capacity; + + do { + --skipfield_pointer; + skipfield_pointer -= *skipfield_pointer; + } while( --distance != 0 ); + + element_pointer = group_pointer->elements + ( skipfield_pointer - group_pointer->skipfield ); + return; + } + } + // Only distance == 0 reaches here + } + + // Advance for reverse_iterator and const_reverse_iterator - this needs to be implemented slightly differently to forward-iterator's advance, as it needs to be able to reach rend() (ie. begin() - 1) and to be bounded by rbegin(): + template + void advance( colony_reverse_iterator &reverse_it, + difference_type distance ) const { // could cause exception if iterator is uninitialized + group_pointer_type &group_pointer = reverse_it.it.group_pointer; + aligned_pointer_type &element_pointer = reverse_it.it.element_pointer; + skipfield_pointer_type &skipfield_pointer = reverse_it.it.skipfield_pointer; + + assert( element_pointer != nullptr ); + + if( distance > 0 ) { + // Check that we're not already at rend() + assert( !( element_pointer == group_pointer->elements - 1 && + group_pointer->previous_group == nullptr ) ); + // Special case for initial element pointer and initial group (we don't know how far into the group the element pointer is) + // Since a reverse_iterator cannot == last_endpoint (ie. before rbegin()) we don't need to check for that like with iterator + if( group_pointer->free_list_head == std::numeric_limits::max() ) { + difference_type distance_from_beginning = static_cast + ( element_pointer - group_pointer->elements ); + + if( distance <= distance_from_beginning ) { + element_pointer -= distance; + skipfield_pointer -= distance; + return; + } else if( group_pointer->previous_group == nullptr ) { + // Either we've reached rend() or gone beyond it, so bound to rend() + element_pointer = group_pointer->elements - 1; + skipfield_pointer = group_pointer->skipfield - 1; + return; + } else { + distance -= distance_from_beginning; + } + } else { + const skipfield_pointer_type beginning_point = group_pointer->skipfield + * + ( group_pointer->skipfield ); + + while( skipfield_pointer != beginning_point ) { + --skipfield_pointer; + skipfield_pointer -= *skipfield_pointer; + + if( --distance == 0 ) { + element_pointer = group_pointer->elements + ( skipfield_pointer - group_pointer->skipfield ); + return; + } + } + + if( group_pointer->previous_group == nullptr ) { + // If we've reached rend(), bound to that + element_pointer = group_pointer->elements - 1; + skipfield_pointer = group_pointer->skipfield - 1; + return; + } + } + + group_pointer = group_pointer->previous_group; + + // Intermediary groups - at the start of this code block and the subsequent block, the position of the iterator is assumed to be the first non-erased element in the next group: + while( static_cast( group_pointer->number_of_elements ) < distance ) { + if( group_pointer->previous_group == nullptr ) { // bound to rend() + element_pointer = group_pointer->elements - 1; + skipfield_pointer = group_pointer->skipfield - 1; + return; + } + + distance -= static_cast( group_pointer->number_of_elements ); + group_pointer = group_pointer->previous_group; + } + + // Final group (if not already reached) + if( static_cast( group_pointer->number_of_elements ) == distance ) { + element_pointer = group_pointer->elements + *( group_pointer->skipfield ); + skipfield_pointer = group_pointer->skipfield + *( group_pointer->skipfield ); + return; + } else if( group_pointer->free_list_head == std::numeric_limits::max() ) { + element_pointer = reinterpret_cast( group_pointer->skipfield ) - distance; + skipfield_pointer = ( group_pointer->skipfield + group_pointer->capacity ) - distance; + return; + } else { + skipfield_pointer = group_pointer->skipfield + group_pointer->capacity; + + do { + --skipfield_pointer; + skipfield_pointer -= *skipfield_pointer; + } while( --distance != 0 ); + + element_pointer = group_pointer->elements + ( skipfield_pointer - group_pointer->skipfield ); + return; + } + } else if( distance < 0 ) { + // Check that we're not already at rbegin() + assert( !( ( element_pointer == ( group_pointer->last_endpoint - 1 ) - * + ( group_pointer->skipfield + ( group_pointer->last_endpoint - group_pointer->elements ) - 1 ) ) && + group_pointer->next_group == nullptr ) ); + + if( element_pointer != group_pointer->elements + * ( group_pointer->skipfield ) ) { + // ie. != first non-erased element in group + if( group_pointer->free_list_head == std::numeric_limits::max() ) { + // ie. if there are no erasures in the group + const difference_type distance_from_end = static_cast + ( group_pointer->last_endpoint - element_pointer ); + + if( distance < distance_from_end ) { + element_pointer += distance; + skipfield_pointer += distance; + return; + } else if( group_pointer->next_group == nullptr ) { // bound to rbegin() + // no erasures so we don't have to subtract skipfield value as we do below + element_pointer = group_pointer->last_endpoint - 1; + skipfield_pointer += distance_from_end - 1; + return; + } else { + distance -= distance_from_end; + } + } else { + const skipfield_pointer_type endpoint = skipfield_pointer + ( group_pointer->last_endpoint - + element_pointer ); + + while( true ) { + ++skipfield_pointer; + skipfield_pointer += *skipfield_pointer; + --distance; + + if( skipfield_pointer == endpoint ) { + break; + } else if( distance == 0 ) { + element_pointer = group_pointer->elements + ( skipfield_pointer - group_pointer->skipfield ); + return; + } + } + + if( group_pointer->next_group == nullptr ) { // bound to rbegin() + --skipfield_pointer; + element_pointer = ( group_pointer->last_endpoint - 1 ) - *skipfield_pointer; + skipfield_pointer -= *skipfield_pointer; + return; + } + } + + group_pointer = group_pointer->next_group; + + if( distance == 0 ) { + element_pointer = group_pointer->elements + *( group_pointer->skipfield ); + skipfield_pointer = group_pointer->skipfield + *( group_pointer->skipfield ); + return; + } + } + + // Intermediary groups - at the start of this code block and the subsequent block, the position of the iterator is assumed to be the first non-erased element in the current group, as a result of the previous code blocks: + while( static_cast( group_pointer->number_of_elements ) <= distance ) { + if( group_pointer->next_group == nullptr ) { // bound to rbegin() + skipfield_pointer = group_pointer->skipfield + ( group_pointer->last_endpoint - + group_pointer->elements ) - 1; + --skipfield_pointer; + element_pointer = ( group_pointer->last_endpoint - 1 ) - *skipfield_pointer; + skipfield_pointer -= *skipfield_pointer; + return; + } else if( ( distance -= group_pointer->number_of_elements ) == 0 ) { + group_pointer = group_pointer->next_group; + element_pointer = group_pointer->elements + *( group_pointer->skipfield ); + skipfield_pointer = group_pointer->skipfield + *( group_pointer->skipfield ); + return; + } else { + group_pointer = group_pointer->next_group; + } + } + + // Final group (if not already reached): + if( group_pointer->free_list_head == std::numeric_limits::max() ) { + // No erasures in this group, use straight pointer addition + element_pointer = group_pointer->elements + distance; + skipfield_pointer = group_pointer->skipfield + distance; + return; + } else { // ie. number_of_elements > distance - safe to ignore endpoint check condition while incrementing: + skipfield_pointer = group_pointer->skipfield + *( group_pointer->skipfield ); + + do { + ++skipfield_pointer; + skipfield_pointer += *skipfield_pointer; + } while( --distance != 0 ); + + element_pointer = group_pointer->elements + ( skipfield_pointer - group_pointer->skipfield ); + return; + } + + return; + } + } + + /** + * Creates a copy of the iterator supplied, then increments/decrements this iterator by + * the positive or negative amount indicated by distance. + */ + template + inline colony_iterator next( const colony_iterator &it, + const typename colony_iterator::difference_type distance = 1 ) const { + colony_iterator return_iterator( it ); + advance( return_iterator, distance ); + return return_iterator; + } + + /** + * Creates a copy of the iterator supplied, then increments/decrements this iterator by + * the positive or negative amount indicated by distance. + */ + template + inline colony_reverse_iterator next( const colony_reverse_iterator &it, + const typename colony_reverse_iterator::difference_type distance = 1 ) const { + colony_reverse_iterator return_iterator( it ); + advance( return_iterator, distance ); + return return_iterator; + } + + /** + * Creates a copy of the iterator supplied, then decrements/increments this iterator by the + * positive or negative amount indicated by distance. + */ + template + inline colony_iterator prev( const colony_iterator &it, + const typename colony_iterator::difference_type distance = 1 ) const { + colony_iterator return_iterator( it ); + advance( return_iterator, -distance ); + return return_iterator; + } + + /** + * Creates a copy of the iterator supplied, then decrements/increments this iterator by the + * positive or negative amount indicated by distance. + */ + template + inline colony_reverse_iterator prev( const colony_reverse_iterator &it, + const typename colony_reverse_iterator::difference_type distance = 1 ) const { + colony_reverse_iterator return_iterator( it ); + advance( return_iterator, -distance ); + return return_iterator; + } + + /** + * Measures the distance between two iterators, returning the result, which will be negative + * if the second iterator supplied is before the first iterator supplied in terms of its + * location in the colony. + */ + template + typename colony_iterator::difference_type distance( const colony_iterator + &first, const colony_iterator &last ) const { + // Code logic: + // If iterators are the same, return 0 + // Otherwise, find which iterator is later in colony, copy that to iterator2. Copy the lower to iterator1. + // If they are not pointing to elements in the same group, process the intermediate groups and add distances, + // skipping manual incrementation in all but the initial and final groups. + // In the initial and final groups, manual incrementation must be used to calculate distance, if there have been no prior erasures in those groups. + // If there are no prior erasures in either of those groups, we can use pointer arithmetic to calculate the distances for those groups. + + assert( !( first.group_pointer == nullptr ) && + !( last.group_pointer == nullptr ) ); // Check that they are initialized + + if( last.element_pointer == first.element_pointer ) { + return 0; + } + + using iterator_type = colony_iterator; + using diff_type = typename iterator_type::difference_type; + diff_type distance = 0; + + iterator_type iterator1 = first, iterator2 = last; + const bool swap = first > last; + + if( swap ) { // Less common case + iterator1 = last; + iterator2 = first; + } + + if( iterator1.group_pointer != iterator2.group_pointer ) { + // if not in same group, process intermediate groups + + // Process initial group: + if( iterator1.group_pointer->free_list_head == std::numeric_limits::max() ) { + // If no prior erasures have occured in this group we can do simple addition + distance += static_cast( iterator1.group_pointer->last_endpoint - + iterator1.element_pointer ); + } else if( iterator1.element_pointer == iterator1.group_pointer->elements ) { + // ie. element is at start of group - rare case + distance += static_cast( iterator1.group_pointer->number_of_elements ); + } else { + const skipfield_pointer_type endpoint = iterator1.skipfield_pointer + + ( iterator1.group_pointer->last_endpoint - iterator1.element_pointer ); + + while( iterator1.skipfield_pointer != endpoint ) { + iterator1.skipfield_pointer += *( ++iterator1.skipfield_pointer ); + ++distance; + } + } + + // Process all other intermediate groups: + iterator1.group_pointer = iterator1.group_pointer->next_group; + + while( iterator1.group_pointer != iterator2.group_pointer ) { + distance += static_cast( iterator1.group_pointer->number_of_elements ); + iterator1.group_pointer = iterator1.group_pointer->next_group; + } + + iterator1.skipfield_pointer = iterator1.group_pointer->skipfield; + } + + if( iterator1.group_pointer->free_list_head == std::numeric_limits::max() ) { + // ie. no erasures in this group, direct subtraction is possible + distance += static_cast( iterator2.skipfield_pointer - iterator1.skipfield_pointer ); + } else if( iterator1.group_pointer->last_endpoint - 1 >= iterator2.element_pointer ) { + // ie. if iterator2 is .end() or 1 before + distance += static_cast( iterator1.group_pointer->number_of_elements - + ( iterator1.group_pointer->last_endpoint - iterator2.element_pointer ) ); + } else { + while( iterator1.skipfield_pointer != iterator2.skipfield_pointer ) { + iterator1.skipfield_pointer += *( ++iterator1.skipfield_pointer ); + ++distance; + } + } + + if( swap ) { + distance = -distance; + } + + return distance; + } + + template + inline typename colony_reverse_iterator::difference_type distance( + const colony_reverse_iterator &first, + const colony_reverse_iterator &last ) const { + return distance( last.it, first.it ); + } + + // Type-changing functions: + + /** + * Getting a pointer from an iterator is simple - simply dereference it then grab the + * address ie. "&(*the_iterator);". Getting an iterator from a pointer is typically not so + * simple. This function enables the user to do exactly that. This is expected to be useful + * in the use-case where external containers are storing pointers to colony elements instead + * of iterators (as iterators for colonies have 3 times the size of an element pointer) and + * the program wants to erase the element being pointed to or possibly change the element + * being pointed to. Converting a pointer to an iterator using this method and then erasing, + * is about 20% slower on average than erasing when you already have the iterator. This is + * less dramatic than it sounds, as it is still faster than all std:: container erasure + * times which it is roughly equal to. However this is generally a slower, lookup-based + * operation. If the lookup doesn't find a non-erased element based on that pointer, it + * returns end(). Otherwise it returns an iterator pointing to the element in question. + * + * Cannot be noexcept as colony could be empty or pointer invalid + */ + iterator get_iterator_from_pointer( const pointer element_pointer ) const { + assert( !empty() ); + assert( element_pointer != nullptr ); + + // Start with last group first, as will be the largest group + group_pointer_type current_group = end_iterator.group_pointer; + + while( current_group != nullptr ) { + if( reinterpret_cast( element_pointer ) >= current_group->elements && + reinterpret_cast( element_pointer ) < reinterpret_cast + ( current_group->skipfield ) ) { + const skipfield_pointer_type skipfield_pointer = current_group->skipfield + + ( reinterpret_cast( element_pointer ) - current_group->elements ); + return ( *skipfield_pointer == 0 ) ? iterator( current_group, + reinterpret_cast( element_pointer ), + skipfield_pointer ) : end_iterator; // If element has been erased, return end() + } + + current_group = current_group->previous_group; + } + + return end_iterator; + } + + /** + * While colony is a container with unordered insertion (and is therefore unordered), it + * still has a (transitory) order which changes upon any erasure or insertion. Temporary + * index numbers are therefore obtainable. These can be useful, for example, when creating + * a save file in a computer game, where certain elements in a container may need to be + * re-linked to other elements in other container upon reloading the save file. + * + * may throw exception if iterator is invalid/uninitialized + */ + template + size_type get_index_from_iterator( const colony_iterator &it ) const { + assert( !empty() ); + assert( it.group_pointer != nullptr ); + + // This is essentially a simplified version of distance() optimized for counting from begin() + size_type index = 0; + group_pointer_type group_pointer = begin_iterator.group_pointer; + + // For all prior groups, add group sizes + while( group_pointer != it.group_pointer ) { + index += static_cast( group_pointer->number_of_elements ); + group_pointer = group_pointer->next_group; + } + + if( group_pointer->free_list_head == std::numeric_limits::max() ) { + // If no erased elements in group exist, do straight pointer arithmetic to get distance to start for first element + index += static_cast( it.element_pointer - group_pointer->elements ); + } else { + // Otherwise do manual ++ loop - count from beginning of group until location is reached + skipfield_pointer_type skipfield_pointer = group_pointer->skipfield + *( group_pointer->skipfield ); + + while( skipfield_pointer != it.skipfield_pointer ) { + ++skipfield_pointer; + skipfield_pointer += *skipfield_pointer; + ++index; + } + } + + return index; + } + + /** + * The same as get_index_from_iterator, but for reverse_iterators and const_reverse_iterators. + * Index is from front of colony (same as iterator), not back of colony. + */ + template + inline size_type get_index_from_reverse_iterator( const colony_reverse_iterator + &rev_iterator ) const { + return get_index_from_iterator( rev_iterator.it ); + } + + /** + * As described above, there may be situations where obtaining iterators to specific + * elements based on an index can be useful, for example, when reloading save files. This + * function is basically a shorthand to avoid typing + * iterator it = colony.begin(); + * colony.advance(it, index); + * + * Cannot be noexcept as colony could be empty + */ + template + iterator get_iterator_from_index( const index_type index ) const { + assert( !empty() ); + assert( std::numeric_limits::is_integer ); + + iterator it( begin_iterator ); + advance( it, static_cast( index ) ); + return it; + } + + /** Returns a copy of the allocator used by the colony instance. */ + inline allocator_type get_allocator() const noexcept { + return element_allocator_type(); + } + + private: + + struct less { + bool operator()( const element_type &a, const element_type &b ) const noexcept { + return a < b; + } + }; + + // Function-object, used to redirect the sort function to compare element pointers by the elements they point to, and sort the element pointers instead of the elements: + template + struct sort_dereferencer { + comparison_function stored_instance; + + explicit sort_dereferencer( const comparison_function &function_instance ): + stored_instance( function_instance ) + {} + + sort_dereferencer() noexcept = default; + + bool operator()( const pointer first, const pointer second ) { + return stored_instance( *first, *second ); + } + }; + + public: + + /** + * Sort the content of the colony. By default this compares the colony content using a + * less-than operator, unless the user supplies a comparison function (ie. same conditions + * as std::list's sort function). + * + * Uses std::sort internally. + */ + template + void sort( comparison_function compare ) { + if( total_number_of_elements < 2 ) { + return; + } + + pointer *const element_pointers = COLONY_ALLOCATE( pointer_allocator_type, pointer_allocator_pair, + total_number_of_elements, nullptr ); + pointer *element_pointer = element_pointers; + + // Construct pointers to all elements in the colony in sequence: + for( iterator current_element = begin_iterator; current_element != end_iterator; + ++current_element ) { + COLONY_CONSTRUCT( pointer_allocator_type, pointer_allocator_pair, element_pointer++, + &*current_element ); + } + + // Now, sort the pointers by the values they point to: + std::sort( element_pointers, element_pointers + total_number_of_elements, + sort_dereferencer( compare ) ); + + // Create a new colony and copy the elements from the old one to the new one in the order of the sorted pointers: + colony new_location; + new_location.change_group_sizes( pointer_allocator_pair.min_elements_per_group, + group_allocator_pair.max_elements_per_group ); + + try { + new_location.reserve( static_cast( ( total_number_of_elements > + std::numeric_limits::max() ) ? std::numeric_limits::max() : + total_number_of_elements ) ); + + if COLONY_CONSTEXPR( std::is_move_constructible::value ) { + for( pointer *current_element_pointer = element_pointers; + current_element_pointer != element_pointer; ++current_element_pointer ) { + new_location.insert( std::move( *reinterpret_cast( *current_element_pointer ) ) ); + } + } else { + for( pointer *current_element_pointer = element_pointers; + current_element_pointer != element_pointer; ++current_element_pointer ) { + new_location.insert( *reinterpret_cast( *current_element_pointer ) ); + } + } + } catch( ... ) { + if COLONY_CONSTEXPR( !std::is_trivially_destructible::value ) { + for( pointer *current_element_pointer = element_pointers; + current_element_pointer != element_pointer; ++current_element_pointer ) { + COLONY_DESTROY( pointer_allocator_type, pointer_allocator_pair, current_element_pointer ); + } + } + + COLONY_DEALLOCATE( pointer_allocator_type, pointer_allocator_pair, element_pointers, + total_number_of_elements ); + throw; + } + + // Make the old colony the new one, destroy the old one's data/sequence: + *this = std::move( new_location ); // avoid generating temporary + + if COLONY_CONSTEXPR( !std::is_trivially_destructible::value ) { + for( pointer *current_element_pointer = element_pointers; + current_element_pointer != element_pointer; ++current_element_pointer ) { + COLONY_DESTROY( pointer_allocator_type, pointer_allocator_pair, current_element_pointer ); + } + } + + COLONY_DEALLOCATE( pointer_allocator_type, pointer_allocator_pair, element_pointers, + total_number_of_elements ); + } + + inline void sort() { + sort( less() ); + } + + /** + * Transfer all elements from source colony into destination colony without invalidating + * pointers/iterators to either colony's elements (in other words the destination takes + * ownership of the source's memory blocks). After the splice, the source colony is empty. + * Splicing is much faster than range-moving or copying all elements from one colony to + * another. Colony does not guarantee a particular order of elements after splicing, for + * performance reasons; the insertion location of source elements in the destination colony + * is chosen based on the most positive performance outcome for subsequent iterations/ + * insertions. For example if the destination colony is {1, 2, 3, 4} and the source colony + * is {5, 6, 7, 8} the destination colony post-splice could be {1, 2, 3, 4, 5, 6, 7, 8} or + * {5, 6, 7, 8, 1, 2, 3, 4}, depending on internal state of both colonies and prior + * insertions/erasures. + */ + void splice( colony &source ) COLONY_NOEXCEPT_SWAP( allocator_type ) { + // Process: if there are unused memory spaces at the end of the current back group of the chain, convert them + // to skipped elements and add the locations to the group's free list. + // Then link the destination's groups to the source's groups and nullify the source. + // If the source has more unused memory spaces in the back group than the destination, swap them before processing to reduce the number of locations added to a free list and also subsequent jumps during iteration. + + assert( &source != this ); + + if( source.total_number_of_elements == 0 ) { + return; + } else if( total_number_of_elements == 0 ) { + *this = std::move( source ); + return; + } + + // If there's more unused element locations at end of destination than source, swap with source to reduce number of skipped elements and size of free-list: + if( ( reinterpret_cast( end_iterator.group_pointer->skipfield ) - + end_iterator.element_pointer ) > ( reinterpret_cast + ( source.end_iterator.group_pointer->skipfield ) - source.end_iterator.element_pointer ) ) { + swap( source ); + } + + // Correct group sizes if necessary: + if( source.pointer_allocator_pair.min_elements_per_group < + pointer_allocator_pair.min_elements_per_group ) { + pointer_allocator_pair.min_elements_per_group = + source.pointer_allocator_pair.min_elements_per_group; + } + + if( source.group_allocator_pair.max_elements_per_group > + group_allocator_pair.max_elements_per_group ) { + group_allocator_pair.max_elements_per_group = source.group_allocator_pair.max_elements_per_group; + } + + // Add source list of groups-with-erasures to destination list of groups-with-erasures: + if( source.groups_with_erasures_list_head != nullptr ) { + if( groups_with_erasures_list_head != nullptr ) { + group_pointer_type tail_group = groups_with_erasures_list_head; + + while( tail_group->erasures_list_next_group != nullptr ) { + tail_group = tail_group->erasures_list_next_group; + } + + tail_group->erasures_list_next_group = source.groups_with_erasures_list_head; + } else { + groups_with_erasures_list_head = source.groups_with_erasures_list_head; + } + } + + const skipfield_type distance_to_end = static_cast + ( reinterpret_cast( end_iterator.group_pointer->skipfield ) - + end_iterator.element_pointer ); + + if( distance_to_end != 0 ) { // 0 == edge case + // Mark unused element memory locations from back group as skipped/erased: + + // Update skipfield: + const skipfield_type previous_node_value = *( end_iterator.skipfield_pointer - 1 ); + end_iterator.group_pointer->last_endpoint = reinterpret_cast + ( end_iterator.group_pointer->skipfield ); + + if( previous_node_value == 0 ) { // no previous skipblock + *end_iterator.skipfield_pointer = distance_to_end; + *( end_iterator.skipfield_pointer + distance_to_end - 1 ) = distance_to_end; + + const skipfield_type index = static_cast( end_iterator.element_pointer - + end_iterator.group_pointer->elements ); + + if( end_iterator.group_pointer->free_list_head != std::numeric_limits::max() ) { + // ie. if this group already has some erased elements + // set prev free list head's 'next index' number to the index of the current element + *( reinterpret_cast( end_iterator.group_pointer->elements + + end_iterator.group_pointer->free_list_head ) + 1 ) = index; + } else { + // add it to the groups-with-erasures free list + end_iterator.group_pointer->erasures_list_next_group = groups_with_erasures_list_head; + groups_with_erasures_list_head = end_iterator.group_pointer; + } + + *( reinterpret_cast( end_iterator.element_pointer ) ) = + end_iterator.group_pointer->free_list_head; + *( reinterpret_cast( end_iterator.element_pointer ) + 1 ) = + std::numeric_limits::max(); + end_iterator.group_pointer->free_list_head = index; + } else { + // update previous skipblock, no need to update free list: + *( end_iterator.skipfield_pointer - previous_node_value ) = *( end_iterator.skipfield_pointer + + distance_to_end - 1 ) = previous_node_value + distance_to_end; + } + } + + // Update subsequent group numbers: + group_pointer_type current_group = source.begin_iterator.group_pointer; + size_type current_group_number = end_iterator.group_pointer->group_number; + + do { + current_group->group_number = ++current_group_number; + current_group = current_group->next_group; + } while( current_group != nullptr ); + + // Join the destination and source group chains: + end_iterator.group_pointer->next_group = source.begin_iterator.group_pointer; + source.begin_iterator.group_pointer->previous_group = end_iterator.group_pointer; + end_iterator = source.end_iterator; + total_number_of_elements += source.total_number_of_elements; + source.blank(); + } + + /** Swaps the colony's contents with that of source. */ + void swap( colony &source ) COLONY_NOEXCEPT_SWAP( allocator_type ) { + assert( &source != this ); + + // if all pointer types are trivial we can just copy using memcpy - avoids constructors/destructors etc and is faster + if COLONY_CONSTEXPR( std::is_trivial::value && + std::is_trivial::value && + std::is_trivial::value ) { + char temp[sizeof( colony )]; + std::memcpy( &temp, static_cast( this ), sizeof( colony ) ); + std::memcpy( static_cast( this ), static_cast( &source ), sizeof( colony ) ); + std::memcpy( static_cast( &source ), &temp, sizeof( colony ) ); + + // If pointer types are not trivial, moving them is probably going to be more efficient than copying them below + } else if COLONY_CONSTEXPR( std::is_move_assignable::value && + std::is_move_assignable::value && + std::is_move_assignable::value && + std::is_move_constructible::value && + std::is_move_constructible::value && + std::is_move_constructible::value ) { + colony temp( std::move( source ) ); + source = std::move( *this ); + *this = std::move( temp ); + } else { + const iterator swap_end_iterator = end_iterator; + const iterator swap_begin_iterator = begin_iterator; + const group_pointer_type swap_groups_with_erasures_list_head = groups_with_erasures_list_head; + const size_type swap_total_number_of_elements = total_number_of_elements; + const size_type swap_total_capacity = total_capacity; + const skipfield_type swap_min_elements_per_group = pointer_allocator_pair.min_elements_per_group; + const skipfield_type swap_max_elements_per_group = group_allocator_pair.max_elements_per_group; + + end_iterator = source.end_iterator; + begin_iterator = source.begin_iterator; + groups_with_erasures_list_head = source.groups_with_erasures_list_head; + total_number_of_elements = source.total_number_of_elements; + total_capacity = source.total_capacity; + pointer_allocator_pair.min_elements_per_group = + source.pointer_allocator_pair.min_elements_per_group; + group_allocator_pair.max_elements_per_group = source.group_allocator_pair.max_elements_per_group; + + source.end_iterator = swap_end_iterator; + source.begin_iterator = swap_begin_iterator; + source.groups_with_erasures_list_head = swap_groups_with_erasures_list_head; + source.total_number_of_elements = swap_total_number_of_elements; + source.total_capacity = swap_total_capacity; + source.pointer_allocator_pair.min_elements_per_group = swap_min_elements_per_group; + source.group_allocator_pair.max_elements_per_group = swap_max_elements_per_group; + } + } + +}; // colony + +/** + * Swaps colony A's contents with that of colony B. + * Assumes both colonies have same element type, allocator type, etc. + */ +template +inline void swap( colony &a, + colony &b ) COLONY_NOEXCEPT_SWAP( + element_allocator_type ) +{ + a.swap( b ); +} + +} // namespace cata + +#undef COLONY_FORCE_INLINE + +#undef COLONY_NOEXCEPT_SWAP +#undef COLONY_NOEXCEPT_MOVE_ASSIGNMENT +#undef COLONY_CONSTEXPR + +#undef COLONY_CONSTRUCT +#undef COLONY_DESTROY +#undef COLONY_ALLOCATE +#undef COLONY_ALLOCATE_INITIALIZATION +#undef COLONY_DEALLOCATE + +#endif // CATA_SRC_COLONY_H diff --git a/Src/compatibility.h b/Src/compatibility.h new file mode 100644 index 0000000000..df641bfb9e --- /dev/null +++ b/Src/compatibility.h @@ -0,0 +1,138 @@ +#pragma once +#ifndef CATA_SRC_COMPATIBILITY_H +#define CATA_SRC_COMPATIBILITY_H + +//-------------------------------------------------------------------------------------------------- +// HACK: +// std::to_string is broken on MinGW (as of 13/01/2015), but is fixed in MinGW-w64 gcc 4.8. +// It is also broken in Cygwin with no fix at this time. +// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52015 +// This is a minimal workaround pending the issue begin solved (if ever). +// For proper linking these must be declared inline here, or moved to a cpp file. +//-------------------------------------------------------------------------------------------------- +#include + +#define CATA_GCC_VER ((__GNUC__ * 10000) + (__GNUC_MINOR__ * 100) + (__GNUC_PATCHLEVEL__)) + +#if defined(__MINGW32__) && !defined(__MINGW64__) +# define CATA_NO_CPP11_STRING_CONVERSIONS +#elif defined(__MINGW64__) && (CATA_GCC_VER < 40800) +# define CATA_NO_CPP11_STRING_CONVERSIONS +#elif defined(__GNUC__) && !defined(__clang__) && (CATA_GCC_VER < 40800) +# define CATA_NO_ADVANCE +#endif +// CATA_NO_CPP11_STRING_CONVERSIONS is also defined in Makefile for TARGETSYSTEM=CYGWIN + +#if defined(CATA_NO_CPP11_STRING_CONVERSIONS) +#include +#include + +// NOLINTNEXTLINE(cata-no-long) +inline std::string to_string( const long n ) +{ + //- and \0 + constexpr int size = std::numeric_limits::digits10 + 2; + char buffer[size]; + snprintf( buffer, size, "%ld", n ); + return buffer; +} + +// NOLINTNEXTLINE(cata-no-long) +inline std::string to_string( const unsigned long n ) +{ + //- and \0 + constexpr int size = std::numeric_limits::digits10 + 2; + char buffer[size]; + snprintf( buffer, size, "%lu", n ); + return buffer; +} + +inline std::string to_string( const long long n ) +{ + //- and \0 + constexpr int size = std::numeric_limits::digits10 + 2; + char buffer[size]; + const char *format; +#if !defined(__USE_MINGW_ANSI_STDIO) && (defined(__MINGW32__) || defined(__MINGW64__)) + if( sizeof( signed long long int ) == 4 ) { + format = "%I32d"; + } else { + format = "%I64d"; + } +#else + format = "%lld"; +#endif + snprintf( buffer, size, format, n ); + return buffer; +} + +inline std::string to_string( const unsigned long long n ) +{ + //- and \0 + constexpr int size = std::numeric_limits::digits10 + 2; + char buffer[size]; + const char *format; +#if !defined(__USE_MINGW_ANSI_STDIO) && (defined(__MINGW32__) || defined(__MINGW64__)) + if( sizeof( signed long long int ) == 4 ) { + format = "%I32u"; + } else { + format = "%I64u"; + } +#else + format = "%llu"; +#endif + snprintf( buffer, size, format, n ); + return buffer; +} + +inline std::string to_string( const int n ) +{ + //- and \0 + constexpr int size = std::numeric_limits::digits10 + 2; + char buffer[size]; + snprintf( buffer, size, "%d", n ); + return buffer; +} + +inline std::string to_string( unsigned const int n ) +{ + //+ and \0 (no -) + constexpr int size = std::numeric_limits::digits10 + 2; + char buffer[size]; + snprintf( buffer, size, "%u", n ); + return buffer; +} + +inline std::string to_string( const double n ) +{ + //- . \0 + snprintf default precision. + constexpr int size = std::numeric_limits::max_exponent10 + 6 + 3; + char buffer[size]; + snprintf( buffer, size, "%f", n ); + return buffer; +} +#else //all other platforms +#include + +//mirrors the valid overloads of std::to_string +template < typename T, typename std::enable_if < std::is_arithmetic::value && + !std::is_same::value &&!std::is_same::value && + !std::is_same::value &&!std::is_same::value && + !std::is_same::value >::type * = nullptr > +std::string to_string( T n ) +{ + return std::to_string( n ); +} +#endif //CATA_NO_CPP11_STRING_CONVERSIONS + +#if defined(CATA_NO_ADVANCE) +template +inline void std::advance( I iter, int num ) +{ + while( num-- > 0 ) { + iter++; + } +} +#endif //CATA_NO_ADVANCE + +#endif // CATA_SRC_COMPATIBILITY_H diff --git a/Src/debug.h b/Src/debug.h new file mode 100644 index 0000000000..f6d4834ed7 --- /dev/null +++ b/Src/debug.h @@ -0,0 +1,221 @@ +#pragma once +#ifndef CATA_SRC_DEBUG_H +#define CATA_SRC_DEBUG_H + +#include "string_formatter.h" + +/** + * debugmsg(msg, ...) + * varg-style functions: the first argument is the format (see printf), + * the other optional arguments must match that format. + * The message is formatted and printed on screen to player, they must + * acknowledge the message by pressing SPACE. This can be quite annoying, so + * avoid this function if possible. + * The message is also logged with DebugLog, a separate call to DebugLog + * is not needed. + * + * DebugLog + * The main debugging system. It uses debug levels (how critical/important + * the message is) and debug classes (which part of the code it comes from, + * e.g. mapgen, SDL, npcs). This allows disabling debug classes and levels + * (e.g. only write SDL messages, but no npc or map related ones). + * It returns a reference to an outputs stream. Simply write your message into + * that stream (using the << operators). + * DebugLog always returns a stream that starts on a new line. Don't add a + * newline at the end of your debug message. + * If the specific debug level or class have been disabled, the message is + * actually discarded, otherwise it is written to a log file. + * If a single source file contains mostly messages for the same debug class + * (e.g. mapgen.cpp), create and use the macro dbg. + * + * dbg + * Usually a single source contains only debug messages for a single debug class + * (e.g. mapgen.cpp contains only messages for D_MAP_GEN, npcmove.cpp only D_NPC). + * Those files contain a macro at top: +@code +#define dbg(x) DebugLog((x), D_NPC) << __FILE__ << ":" << __LINE__ << ": " +@endcode + * It allows to call the debug system and just supply the debug level, the debug + * class is automatically inserted as it is the same for the whole file. Also this + * adds the file name and the line of the statement to the debug message. + * This can be replicated in any source file, just copy the above macro and change + * D_NPC to the debug class to use. Don't add this macro to a header file + * as the debug class is source file specific. + * As dbg calls DebugLog, it returns the stream, its usage is the same. + */ + +// Includes {{{1 +// --------------------------------------------------------------------- +#include +#include +#include +#include +#include + +#define STRING2(x) #x +#define STRING(x) STRING2(x) + +#if defined(__GNUC__) +#define __FUNCTION_NAME__ __PRETTY_FUNCTION__ +#else +#define __FUNCTION_NAME__ __func__ +#endif + +/** + * Debug message of level D_ERROR and class D_MAIN, also includes the source + * file name and line, uses varg style arguments, teh first argument must be + * a printf style format string. + */ + +#define debugmsg(...) realDebugmsg(__FILE__, STRING(__LINE__), __FUNCTION_NAME__, __VA_ARGS__) + +// Don't use this, use debugmsg instead. +void realDebugmsg( const char *filename, const char *line, const char *funcname, + const std::string &text ); +template +inline void realDebugmsg( const char *const filename, const char *const line, + const char *const funcname, const char *const mes, Args &&... args ) +{ + return realDebugmsg( filename, line, funcname, string_format( mes, + std::forward( args )... ) ); +} + +// A fatal error for use in constexpr functions +// This exists for compatibility reasons. On gcc 5.3 we need a +// different implementation that is messier. +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67371 +// Pass a placeholder return value to be used on gcc 5.3 (it won't +// actually be returned, it's just needed for the type), and then +// args as if to debugmsg for the remaining args. +#if defined(__GNUC__) && __GNUC__ < 6 +#define constexpr_fatal(ret, ...) \ + do { return false ? ( ret ) : ( abort(), ( ret ) ); } while(false) +#else +#define constexpr_fatal(ret, ...) \ + do { debugmsg(__VA_ARGS__); abort(); } while(false) +#endif + +/** + * Used to generate game report information. + */ +namespace game_info +{ +/** Return the name of the current operating system. + */ +std::string operating_system(); +/** Return a detailed version of the operating system; e.g. "Ubuntu 18.04" or "(Windows) 10 1809". + */ +std::string operating_system_version(); +/** Return the "bitness" of the game (not necessarily of the operating system); either: 64-bit, 32-bit or Unknown. + */ +std::string bitness(); +/** Return the game version, as in the entry screen. + */ +std::string game_version(); +/** Return the underlying graphics version used by the game; either Tiles or Curses. +*/ +std::string graphics_version(); +/** Return a list of the loaded mods, including the mod full name and its id name in brackets, e.g. "Dark Days Ahead [dda]". +*/ +std::string mods_loaded(); +/** Generate a game report, including the information returned by all of the other functions. + */ +std::string game_report(); +} // namespace game_info + +// Enumerations {{{1 +// --------------------------------------------------------------------- + +/** + * If you add an entry, add an entry in that function: + * std::ostream &operator<<(std::ostream &out, DebugLevel lev) + */ +enum DebugLevel { + D_INFO = 1, + D_WARNING = 1 << 2, + D_ERROR = 1 << 3, + D_PEDANTIC_INFO = 1 << 4, + + DL_ALL = ( 1 << 5 ) - 1 +}; + +inline DebugLevel operator|( DebugLevel l, DebugLevel r ) +{ + return static_cast( + static_cast>( l ) | r ); +} + +/** + * Debugging areas can be enabled for each of those areas separately. + * If you add an entry, add an entry in that function: + * std::ostream &operator<<(std::ostream &out, DebugClass cl) + */ +enum DebugClass { + /** Messages from realDebugmsg */ + D_MAIN = 1, + /** Related to map and mapbuffer (map.cpp, mapbuffer.cpp) */ + D_MAP = 1 << 2, + /** Mapgen (mapgen*.cpp), also overmap.cpp */ + D_MAP_GEN = 1 << 3, + /** Main game class */ + D_GAME = 1 << 4, + /** npcs*.cpp */ + D_NPC = 1 << 5, + /** SDL & tiles & anything graphical */ + D_SDL = 1 << 6, + + DC_ALL = ( 1 << 30 ) - 1 +}; + +enum class DebugOutput : int { + std_err, + file, +}; + +/** Initializes the debugging system, called exactly once from main() */ +void setupDebug( DebugOutput ); +/** Opposite of setupDebug, shuts the debugging system down. */ +void deinitDebug(); + +// Function Declarations {{{1 +// --------------------------------------------------------------------- +/** + * Set debug levels that should be logged. bitmask is a OR-combined + * set of DebugLevel values. Use 0 to disable all. + * Note that D_ERROR is always logged. + */ +void limitDebugLevel( int ); +/** + * Set the debug classes should be logged. bitmask is a OR-combined + * set of DebugClass values. Use 0 to disable all. + * Note that D_UNSPECIFIC is always logged. + */ +void limitDebugClass( int ); + +/** + * @return true if any error has been logged in this run. + */ +bool debug_has_error_been_observed(); + +// Debug Only {{{1 +// --------------------------------------------------------------------- + +// See documentation at the top. +std::ostream &DebugLog( DebugLevel, DebugClass ); + +/** + * Extended debugging mode, can be toggled during game. + * If enabled some debug message in the normal player message log are shown, + * and other windows might have verbose display (e.g. vehicle window). + */ +extern bool debug_mode; + +#if defined(BACKTRACE) +/** + * Write a stack backtrace to the given ostream + */ +void debug_write_backtrace( std::ostream &out ); +#endif + +// vim:tw=72:sw=4:fdm=marker:fdl=0: +#endif // CATA_SRC_DEBUG_H diff --git a/Src/enum_conversions.h b/Src/enum_conversions.h new file mode 100644 index 0000000000..774225c430 --- /dev/null +++ b/Src/enum_conversions.h @@ -0,0 +1,87 @@ +#pragma once +#ifndef CATA_SRC_ENUM_CONVERSIONS_H +#define CATA_SRC_ENUM_CONVERSIONS_H + +#include + +#include "debug.h" +#include "enum_traits.h" + +namespace io +{ +/** + * @name Enumeration (de)serialization to/from string. + * + * @ref enum_to_string converts an enumeration value to a string (which can be written to JSON). + * The result must be an non-empty string. + * + * This function must be implemented somewhere for each enumeration type for + * which conversion is required. + * + * Such enums must also specialize enum_traits to specify their 'last' member. + * + * @ref string_to_enum converts the string value back into an enumeration value. The input + * is expected to be one of the outputs of @ref enum_to_string. If the given string does + * not match an enumeration, an @ref InvalidEnumString is thrown. + * + * @code string_to_enum(enum_to_string(X)) == X @endcode must yield true for all values + * of the enumeration E. + */ +/*@{*/ +class InvalidEnumString : public std::runtime_error +{ + public: + InvalidEnumString() : std::runtime_error( "invalid enum string" ) { } + InvalidEnumString( const std::string &msg ) : std::runtime_error( msg ) { } +}; + +template +std::string enum_to_string( E ); + +template +std::unordered_map build_enum_lookup_map() +{ + static_assert( std::is_enum::value, "E should be an enum type" ); + static_assert( has_enum_traits::value, "enum E needs a specialization of enum_traits" ); + std::unordered_map result; + + using Int = std::underlying_type_t; + constexpr Int max = static_cast( enum_traits::last ); + + for( Int i = 0; i < max; ++i ) { + E e = static_cast( i ); + auto inserted = result.emplace( enum_to_string( e ), e ); + if( !inserted.second ) { + debugmsg( "repeated enum string %s (%d and %d)", inserted.first->first, + static_cast( inserted.first->second ), i ); + abort(); + } + } + + return result; +} + +// Helper function to do the lookup in an associative container +template +inline E string_to_enum_look_up( const C &container, const std::string &data ) +{ + const auto iter = container.find( data ); + if( iter == container.end() ) { + throw InvalidEnumString( "Invalid enum string '" + data + "' for '" + + typeid( E ).name() + "'" ); + } + return iter->second; +} + +template +E string_to_enum( const std::string &data ) +{ + static const std::unordered_map string_to_enum_map = + build_enum_lookup_map(); + return string_to_enum_look_up( string_to_enum_map, data ); +} + +/*@}*/ +} // namespace io + +#endif // CATA_SRC_ENUM_CONVERSIONS_H diff --git a/Src/enum_traits.h b/Src/enum_traits.h new file mode 100644 index 0000000000..f89069b161 --- /dev/null +++ b/Src/enum_traits.h @@ -0,0 +1,24 @@ +#pragma once +#ifndef CATA_SRC_ENUM_TRAITS_H +#define CATA_SRC_ENUM_TRAITS_H + +#include + +template +struct enum_traits; + +namespace enum_traits_detail +{ + +template +using last_type = typename std::decay::last )>::type; + +} // namespace enum_traits_detail + +template +struct has_enum_traits : std::false_type {}; + +template +struct has_enum_traits> : std::true_type {}; + +#endif // CATA_SRC_ENUM_TRAITS_H diff --git a/Src/game.h b/Src/game.h new file mode 100644 index 0000000000..f55fd366d4 --- /dev/null +++ b/Src/game.h @@ -0,0 +1,1126 @@ +#pragma once +#ifndef CATA_SRC_GAME_H +#define CATA_SRC_GAME_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "action.h" +#include "calendar.h" +#include "character_id.h" +#include "coordinates.h" +#include "creature.h" +#include "cursesdef.h" +#include "enums.h" +#include "game_constants.h" +#include "item_location.h" +#include "memory_fast.h" +#include "monster.h" +#include "optional.h" +#include "pimpl.h" +#include "point.h" +#include "type_id.h" +#include "weather.h" + +class Character; +class Creature_tracker; +class item; +class location; +class spell_events; +class viewer; + +static constexpr int DEFAULT_TILESET_ZOOM = 16; + +static const std::string SAVE_MASTER( "master.gsav" ); +static const std::string SAVE_ARTIFACTS( "artifacts.gsav" ); +static const std::string SAVE_EXTENSION( ".sav" ); +static const std::string SAVE_EXTENSION_MAP_MEMORY( ".mm" ); +static const std::string SAVE_EXTENSION_LOG( ".log" ); +static const std::string SAVE_EXTENSION_WEATHER( ".weather" ); +static const std::string SAVE_EXTENSION_SHORTCUTS( ".shortcuts" ); + +extern bool test_mode; + +// The reference to the one and only game instance. +class game; + +extern std::unique_ptr g; + +extern bool use_tiles; +extern bool fov_3d; +extern int fov_3d_z_range; +extern bool tile_iso; + +extern const int core_version; + +extern const int savegame_version; +extern int savegame_loading_version; + +class input_context; + +input_context get_default_mode_input_context(); + +enum class dump_mode : int { + TSV, + HTML +}; + +enum quit_status { + QUIT_NO = 0, // Still playing + QUIT_SUICIDE, // Quit with 'Q' + QUIT_SAVED, // Saved and quit + QUIT_NOSAVED, // Quit without saving + QUIT_DIED, // Actual death + QUIT_WATCH, // Died, and watching aftermath +}; + +enum safe_mode_type { + SAFE_MODE_OFF = 0, // Moving always allowed + SAFE_MODE_ON = 1, // Moving allowed, but if a new monsters spawns, go to SAFE_MODE_STOP + SAFE_MODE_STOP = 2, // New monsters spotted, no movement allowed +}; + +enum action_id : int; + +class achievements_tracker; +class avatar; +class event_bus; +class faction_manager; +class kill_tracker; +class map; +class map_item_stack; +class memorial_logger; +class npc; +class player; +class save_t; +class scenario; +class stats_tracker; +class vehicle; +struct WORLD; +struct special_game; +template +class tripoint_range; + +using WORLDPTR = WORLD *; +class live_view; +class loading_ui; +class overmap; +class scent_map; +class static_popup; +class timed_event_manager; +class ui_adaptor; +struct visibility_variables; + +using item_filter = std::function; + +enum peek_act : int { + PA_BLIND_THROW + // obvious future additional value is PA_BLIND_FIRE +}; + +struct look_around_result { + cata::optional position; + cata::optional peek_action; +}; + +struct w_map { + int id; + std::string name; + bool toggle; + catacurses::window win; +}; + +bool is_valid_in_w_terrain( const point &p ); + +// There is only one game instance, so losing a few bytes of memory +// due to padding is not much of a concern. +// NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) +class game +{ + friend class editmap; + friend class advanced_inventory; + friend class main_menu; + friend achievements_tracker &get_achievements(); + friend event_bus &get_event_bus(); + friend map &get_map(); + friend Character &get_player_character(); + friend avatar &get_avatar(); + friend location &get_player_location(); + friend viewer &get_player_view(); + friend weather_manager &get_weather(); + friend const scenario *get_scenario(); + friend void set_scenario( const scenario *new_scenario ); + friend stats_tracker &get_stats(); + friend scent_map &get_scent(); + friend timed_event_manager &get_timed_events(); + friend memorial_logger &get_memorial(); + public: + game(); + ~game(); + + /** Loads static data that does not depend on mods or similar. */ + void load_static_data(); + + /** Loads core dynamic data. May throw. */ + void load_core_data( loading_ui &ui ); + + /** Returns whether the core data is currently loaded. */ + bool is_core_data_loaded() const; + + /** + * Check if mods can be successfully loaded + * @param opts check specific mods (or all if unspecified) + * @return whether all mods were successfully loaded + */ + bool check_mod_data( const std::vector &opts, loading_ui &ui ); + + /** Loads core data and mods from the active world. May throw. */ + void load_world_modfiles( loading_ui &ui ); + /** + * Load content packs + * @param msg string to display whilst loading prompt + * @param packs content packs to load in correct dependent order + * @param ui structure for load progress display + * @return true if all packs were found, false if any were missing + */ + bool load_packs( const std::string &msg, const std::vector &packs, loading_ui &ui ); + + /** + * @brief Should be invoked whenever options change. + */ + void on_options_changed(); + + protected: + /** Loads dynamic data from the given directory. May throw. */ + void load_data_from_dir( const std::string &path, const std::string &src, loading_ui &ui ); + public: + /** Initializes the UI. */ + void init_ui( bool resized = false ); + void setup(); + /** Saving and loading functions. */ + void serialize( std::ostream &fout ); // for save + void unserialize( std::istream &fin ); // for load + void unserialize_master( std::istream &fin ); // for load + + /** write statistics to stdout and @return true if successful */ + bool dump_stats( const std::string &what, dump_mode mode, const std::vector &opts ); + + /** Returns false if saving failed. */ + bool save(); + + /** Returns a list of currently active character saves. */ + std::vector list_active_characters(); + void write_memorial_file( std::string sLastWords ); + bool cleanup_at_end(); + void start_calendar(); + /** MAIN GAME LOOP. Returns true if game is over (death, saved, quit, etc.). */ + bool do_turn(); + shared_ptr_fast create_or_get_main_ui_adaptor(); + void invalidate_main_ui_adaptor() const; + void mark_main_ui_adaptor_resize() const; + void draw(); + void draw_ter( bool draw_sounds = true ); + void draw_ter( const tripoint ¢er, bool looking = false, bool draw_sounds = true ); + + class draw_callback_t + { + public: + draw_callback_t( const std::function &cb ); + ~draw_callback_t(); + void operator()(); + friend class game; + private: + std::function cb; + bool added = false; + }; + /* Add callback that would be called in `game::draw`. This can be used to + * implement map overlays in game menus. If parameters of the callback changes + * during its lifetime, `invaliate_main_ui_adaptor` has to be called for + * the changes to take effect immediately on the next call to `ui_manager::redraw`. + * Otherwise the callback may not take effect until the main ui is invalidated + * due to resizing or other menus closing. The callback is disabled once all + * shared pointers to the callback are deconstructed, and is removed afterwards. */ + void add_draw_callback( const shared_ptr_fast &cb ); + private: + bool is_looking = false; + std::vector> draw_callbacks; + + public: + // when force_redraw is true, redraw all panel instead of just animated panels + // mostly used after UI updates + void draw_panels( bool force_draw = false ); + /** + * Returns the location where the indicator should go relative to the reality bubble, + * or nothing to indicate no indicator should be drawn. + * Based on the vehicle the player is driving, if any. + * @param next If true, bases it on the vehicle the vehicle will turn to next turn, + * instead of the one it is currently facing. + */ + cata::optional get_veh_dir_indicator_location( bool next ) const; + void draw_veh_dir_indicator( bool next ); + + /** + * Moves the player vertically. + * If force == true then they are falling. + * If peeking == true, forbids some exotic movement options + */ + void vertical_move( int z, bool force, bool peeking = false ); + void start_hauling( const tripoint &pos ); + /** Returns the other end of the stairs (if any). May query, affect u etc. */ + cata::optional find_or_make_stairs( map &mp, int z_after, bool &rope_ladder, + bool peeking ); + /** Actual z-level movement part of vertical_move. Doesn't include stair finding, traps etc. */ + void vertical_shift( int z_after ); + /** Add goes up/down auto_notes (if turned on) */ + void vertical_notes( int z_before, int z_after ); + /** Checks to see if a player can use a computer (not illiterate, etc.) and uses if able. */ + void use_computer( const tripoint &p ); + /** + * @return The living creature with the given id. Returns null if no living + * creature with such an id exists. Never returns a dead creature. + * Currently only the player character and npcs have ids. + */ + template + T * critter_by_id( const character_id &id ); + /** + * Returns the Creature at the given location. Optionally casted to the given + * type of creature: @ref npc, @ref player, @ref monster - if there is a creature, + * but it's not of the requested type, returns nullptr. + * @param allow_hallucination Whether to return monsters that are actually hallucinations. + */ + template + T * critter_at( const tripoint &p, bool allow_hallucination = false ); + template + const T * critter_at( const tripoint &p, bool allow_hallucination = false ) const; + /** + * Returns a shared pointer to the given critter (which can be of any of the subclasses of + * @ref Creature). The function may return an empty pointer if the given critter + * is not stored anywhere (e.g. it was allocated on the stack, not stored in + * the @ref critter_tracker nor in @ref active_npc nor is it @ref u). + */ + template + shared_ptr_fast shared_from( const T &critter ); + + /** + * Adds critters to the reality bubble, creating them if necessary. + * Functions taking a @p id parameter will construct a monster based on that id, + * (with default properties). + * Functions taking a @p mon parameter will use the supplied monster instance instead + * (which must not be null). + * Note: the monster will not be upgraded by these functions, it is placed as is. + * + * @ref place_critter_at will place the creature exactly at the given point. + * + * @ref place_critter_around will place the creature around + * the center @p p within the given @p radius (radius 0 means only the center point is used). + * The chosen point will be as close to the center as possible. + * + * @ref place_critter_within will place the creature at a random point within + * that given range. (All points within have equal probability.) + * + * @return All functions return null if the creature could not be placed (usually because + * the target is not suitable for it: may be a solid wall, or air, or already occupied + * by some creature). + * If the creature has been placed, it returns a pointer to it (which is the same as + * the one contained in @p mon). + */ + /** @{ */ + monster *place_critter_at( const mtype_id &id, const tripoint &p ); + monster *place_critter_at( const shared_ptr_fast &mon, const tripoint &p ); + monster *place_critter_around( const mtype_id &id, const tripoint ¢er, int radius ); + monster *place_critter_around( const shared_ptr_fast &mon, const tripoint ¢er, + int radius ); + monster *place_critter_within( const mtype_id &id, const tripoint_range &range ); + monster *place_critter_within( const shared_ptr_fast &mon, + const tripoint_range &range ); + /** @} */ + /** + * Returns the approximate number of creatures in the reality bubble. + * Because of performance restrictions it may return a slightly incorrect + * values (as it includes dead, but not yet cleaned up creatures). + */ + size_t num_creatures() const; + /** Redirects to the creature_tracker update_pos() function. */ + bool update_zombie_pos( const monster &critter, const tripoint &pos ); + void remove_zombie( const monster &critter ); + /** Redirects to the creature_tracker clear() function. */ + void clear_zombies(); + /** Spawns a hallucination at a determined position. */ + bool spawn_hallucination( const tripoint &p ); + /** Spawns a hallucination at a determined position of a given monster. */ + bool spawn_hallucination( const tripoint &p, const mtype_id &mt ); + /** Finds somewhere to spawn a monster. */ + bool find_nearby_spawn_point( const Character &target, const mtype_id &mt, int min_radius, + int max_radius, tripoint &point ); + /** Swaps positions of two creatures */ + bool swap_critters( Creature &, Creature & ); + + private: + friend class monster_range; + friend class Creature_range; + + template + class non_dead_range + { + public: + std::vector> items; + + class iterator + { + private: + bool valid(); + public: + std::vector> &items; + typename std::vector>::iterator iter; + shared_ptr_fast current; + + iterator( std::vector> &i, + const typename std::vector>::iterator t ) : items( i ), iter( t ) { + while( iter != items.end() && !valid() ) { + ++iter; + } + } + iterator( const iterator & ) = default; + iterator &operator=( const iterator & ) = default; + + bool operator==( const iterator &rhs ) const { + return iter == rhs.iter; + } + bool operator!=( const iterator &rhs ) const { + return !operator==( rhs ); + } + iterator &operator++() { + do { + ++iter; + } while( iter != items.end() && !valid() ); + return *this; + } + T &operator*() const { + return *current; + } + }; + iterator begin() { + return iterator( items, items.begin() ); + } + iterator end() { + return iterator( items, items.end() ); + } + }; + + class monster_range : public non_dead_range + { + public: + monster_range( game &game_ref ); + }; + + class npc_range : public non_dead_range + { + public: + npc_range( game &game_ref ); + }; + + class Creature_range : public non_dead_range + { + private: + shared_ptr_fast u; + + public: + Creature_range( game &game_ref ); + }; + + public: + /** + * Returns an anonymous range that contains all creatures. The range allows iteration + * via a range-based for loop, e.g. `for( Creature &critter : all_creatures() ) { ... }`. + * One shall not store the returned range nor the iterators. + * One can freely remove and add creatures to the game during the iteration. Added + * creatures will not be iterated over. + */ + Creature_range all_creatures(); + /// Same as @ref all_creatures but iterators only over monsters. + monster_range all_monsters(); + /// Same as @ref all_creatures but iterators only over npcs. + npc_range all_npcs(); + + /** + * Returns all creatures matching a predicate. Only living ( not dead ) creatures + * are checked ( and returned ). Returned pointers are never null. + */ + std::vector get_creatures_if( const std::function &pred ); + std::vector get_npcs_if( const std::function &pred ); + /** + * Returns a creature matching a predicate. Only living (not dead) creatures + * are checked. Returns `nullptr` if no creature matches the predicate. + * There is no guarantee which creature is returned when several creatures match. + */ + Creature *get_creature_if( const std::function &pred ); + + /** Returns true if there is no player, NPC, or monster on the tile and move_cost > 0. */ + bool is_empty( const tripoint &p ); + /** Returns true if p is outdoors and it is sunny. */ + bool is_in_sunlight( const tripoint &p ); + /** Returns true if p is indoors, underground, or in a car. */ + bool is_sheltered( const tripoint &p ); + /** + * Revives a corpse at given location. The monster type and some of its properties are + * deducted from the corpse. If reviving succeeds, the location is guaranteed to have a + * new monster there (see @ref critter_at). + * @param p The place where to put the revived monster. + * @param it The corpse item, it must be a valid corpse (see @ref item::is_corpse). + * @return Whether the corpse has actually been redivided. Reviving may fail for many + * reasons, including no space to put the monster, corpse being to much damaged etc. + * If the monster was revived, the caller should remove the corpse item. + * If reviving failed, the item is unchanged, as is the environment (no new monsters). + */ + bool revive_corpse( const tripoint &p, item &it ); + /**Turns Broken Cyborg monster into Cyborg NPC via surgery*/ + void save_cyborg( item *cyborg, const tripoint &couch_pos, player &installer ); + /** Asks if the player wants to cancel their activity, and if so cancels it. */ + bool cancel_activity_query( const std::string &text ); + /** Asks if the player wants to cancel their activity and if so cancels it. Additionally checks + * if the player wants to ignore further distractions. */ + bool cancel_activity_or_ignore_query( distraction_type type, const std::string &text ); + /** Handles players exiting from moving vehicles. */ + void moving_vehicle_dismount( const tripoint &dest_loc ); + + /** Returns the current remotely controlled vehicle. */ + vehicle *remoteveh(); + /** Sets the current remotely controlled vehicle. */ + void setremoteveh( vehicle *veh ); + + /** Returns the next available mission id. */ + int assign_mission_id(); + /** Find the npc with the given ID. Returns NULL if the npc could not be found. Searches all loaded overmaps. */ + npc *find_npc( character_id id ); + /** Makes any nearby NPCs on the overmap active. */ + void load_npcs(); + private: + /** Unloads all NPCs. + * + * If you call this you must later call load_npcs, lest caches get + * rather confused. The tests used to call this a lot when they + * shouldn't. It is now private to reduce the chance of similar + * problems in the future. + */ + void unload_npcs(); + public: + /** Unloads, then loads the NPCs */ + void reload_npcs(); + const kill_tracker &get_kill_tracker() const; + /** Add follower id to set of followers. */ + void add_npc_follower( const character_id &id ); + /** Remove follower id from follower set. */ + void remove_npc_follower( const character_id &id ); + /** Get set of followers. */ + std::set get_follower_list(); + /** validate list of followers to account for overmap buffers */ + void validate_npc_followers(); + void validate_mounted_npcs(); + /** validate towed vehicles so they get linked up again after a load */ + void validate_linked_vehicles(); + /** validate camps to ensure they are on the overmap list */ + void validate_camps(); + /** process vehicles that are following the player */ + void autopilot_vehicles(); + /** Picks and spawns a random fish from the remaining fish list when a fish is caught. */ + void catch_a_monster( monster *fish, const tripoint &pos, player *p, + const time_duration &catch_duration ); + /** + * Get the contiguous fishable locations starting at fish_pos, out to the specificed distance. + * @param distance Distance around the fish_pos to examine for contiguous fishable locations. + * @param fish_pos The location being fished. + * @return A set of locations representing the valid contiguous fishable locations. + */ + std::unordered_set get_fishable_locations( int distance, const tripoint &fish_pos ); + /** + * Get the fishable monsters within the provided fishable locations. + * @param fishable_locations A set of locations which are valid fishable terrain. Any fishable monsters + * are filtered by this collection to determine those which can actually be caught. + * @return Fishable monsters within the specified fishable terrain. + */ + std::vector get_fishable_monsters( std::unordered_set &fishable_locations ); + + /** Flings the input creature in the given direction. */ + void fling_creature( Creature *c, const int &dir, float flvel, bool controlled = false ); + + float natural_light_level( int zlev ) const; + /** Returns coarse number-of-squares of visibility at the current light level. + * Used by monster and NPC AI. + */ + unsigned char light_level( int zlev ) const; + void reset_light_level(); + character_id assign_npc_id(); + Creature *is_hostile_nearby(); + Creature *is_hostile_very_close(); + // Handles shifting coordinates transparently when moving between submaps. + // Helper to make calling with a player pointer less verbose. + point update_map( Character &p ); + point update_map( int &x, int &y ); + void update_overmap_seen(); // Update which overmap tiles we can see + + void peek(); + void peek( const tripoint &p ); + cata::optional look_debug(); + + bool check_zone( const zone_type_id &type, const tripoint &where ) const; + /** Checks whether or not there is a zone of particular type nearby */ + bool check_near_zone( const zone_type_id &type, const tripoint &where ) const; + bool is_zones_manager_open() const; + void zones_manager(); + + // Look at nearby terrain ';', or select zone points + cata::optional look_around(); + look_around_result look_around( bool show_window, tripoint ¢er, + const tripoint &start_point, bool has_first_point, bool select_zone, bool peeking ); + + // Shared method to print "look around" info + void pre_print_all_tile_info( const tripoint &lp, const catacurses::window &w_info, + int &line, int last_line, const visibility_variables &cache ); + + // Shared method to print "look around" info + void print_all_tile_info( const tripoint &lp, const catacurses::window &w_look, + const std::string &area_name, int column, + int &line, int last_line, const visibility_variables &cache ); + + void draw_look_around_cursor( const tripoint &lp, const visibility_variables &cache ); + + /** Long description of (visible) things at tile. */ + void extended_description( const tripoint &p ); + + void draw_trail_to_square( const tripoint &t, bool bDrawX ); + + enum inventory_item_menu_positon { + RIGHT_TERMINAL_EDGE, + LEFT_OF_INFO, + RIGHT_OF_INFO, + LEFT_TERMINAL_EDGE, + }; + int inventory_item_menu( item_location locThisItem, + const std::function &startx = []() { + return 0; + }, + const std::function &width = []() { + return 50; + }, + inventory_item_menu_positon position = RIGHT_OF_INFO ); + + /** Custom-filtered menu for inventory and nearby items and those that within specified radius */ + item_location inv_map_splice( const item_filter &filter, const std::string &title, int radius = 0, + const std::string &none_message = "" ); + + bool has_gametype() const; + special_game_type gametype() const; + + void toggle_fullscreen(); + void toggle_pixel_minimap(); + void reload_tileset(); + void temp_exit_fullscreen(); + void reenter_fullscreen(); + void zoom_in(); + void zoom_out(); + void reset_zoom(); + int get_moves_since_last_save() const; + int get_user_action_counter() const; + + /** Saves a screenshot of the current viewport, as a PNG file, to the given location. + * @param file_path: A full path to the file where the screenshot should be saved. + * @note: Only works for SDL/TILES (otherwise the function returns `false`). A window (more precisely, a viewport) must already exist and the SDL renderer must be valid. + * @returns `true` if the screenshot generation was successful, `false` otherwise. + */ + bool take_screenshot( const std::string &file_path ) const; + + /** + * Load the main map at given location, see @ref map::load, in global, absolute submap + * coordinates. + */ + void load_map( const tripoint_abs_sm &pos_sm ); + /** + * The overmap which contains the center submap of the reality bubble. + */ + overmap &get_cur_om() const; + + /** Get all living player allies */ + std::vector allies(); + // Setter for driving_view_offset + void set_driving_view_offset( const point &p ); + // Calculates the driving_view_offset for the given vehicle + // and sets it (view set_driving_view_offset), if + // the options for this feature is deactivated or if veh is NULL, + // the function set the driving offset to (0,0) + void calc_driving_offset( vehicle *veh = nullptr ); + + /**@}*/ + + void open_gate( const tripoint &p ); + + // Knockback functions: knock target at t along a line, either calculated + // from source position s using force parameter or passed as an argument; + // force determines how far target is knocked, if trajectory is calculated + // force also determines damage along with dam_mult; + // stun determines base number of turns target is stunned regardless of impact + // stun == 0 means no stun, stun == -1 indicates only impact stun (wall or npc/monster) + void knockback( const tripoint &s, const tripoint &t, int force, int stun, int dam_mult ); + void knockback( std::vector &traj, int stun, int dam_mult ); + + // Animation related functions + void draw_bullet( const tripoint &t, int i, const std::vector &trajectory, + char bullet ); + void draw_hit_mon( const tripoint &p, const monster &m, bool dead = false ); + void draw_hit_player( const Character &p, int dam ); + void draw_line( const tripoint &p, const tripoint ¢er_point, + const std::vector &points, bool noreveal = false ); + void draw_line( const tripoint &p, const std::vector &points ); + void draw_weather( const weather_printable &wPrint ); + void draw_sct(); + void draw_zones( const tripoint &start, const tripoint &end, const tripoint &offset ); + // Draw critter (if visible!) on its current position into w_terrain. + // @param center the center of view, same as when calling map::draw + void draw_critter( const Creature &critter, const tripoint ¢er ); + void draw_cursor( const tripoint &p ); + // Draw a highlight graphic at p, for example when examining something. + // TILES only, in curses this does nothing + void draw_highlight( const tripoint &p ); + void draw_radiation_override( const tripoint &p, int rad ); + void draw_terrain_override( const tripoint &p, const ter_id &id ); + void draw_furniture_override( const tripoint &p, const furn_id &id ); + void draw_graffiti_override( const tripoint &p, bool has ); + void draw_trap_override( const tripoint &p, const trap_id &id ); + void draw_field_override( const tripoint &p, const field_type_id &id ); + void draw_item_override( const tripoint &p, const itype_id &id, const mtype_id &mid, bool hilite ); + void draw_vpart_override( const tripoint &p, const vpart_id &id, int part_mod, int veh_dir, + bool hilite, const point &mount ); + void draw_below_override( const tripoint &p, bool draw ); + void draw_monster_override( const tripoint &p, const mtype_id &id, int count, + bool more, Creature::Attitude att ); + + bool is_in_viewport( const tripoint &p, int margin = 0 ) const; + /** + * Check whether movement is allowed according to safe mode settings. + * @return true if the movement is allowed, otherwise false. + */ + bool check_safe_mode_allowed( bool repeat_safe_mode_warnings = true ); + void set_safe_mode( safe_mode_type mode ); + + /** open vehicle interaction screen */ + void exam_vehicle( vehicle &veh, const point &cp = point_zero ); + + // Forcefully close a door at p. + // The function checks for creatures/items/vehicles at that point and + // might kill/harm/destroy them. + // If there still remains something that prevents the door from closing + // (e.g. a very big creatures, a vehicle) the door will not be closed and + // the function returns false. + // If the door gets closed the terrain at p is set to door_type and + // true is returned. + // bash_dmg controls how much damage the door does to the + // creatures/items/vehicle. + // If bash_dmg is 0 or smaller, creatures and vehicles are not damaged + // at all and they will prevent the door from closing. + // If bash_dmg is smaller than 0, _every_ item on the door tile will + // prevent the door from closing. If bash_dmg is 0, only very small items + // will do so, if bash_dmg is greater than 0, items won't stop the door + // from closing at all. + // If the door gets closed the items on the door tile get moved away or destroyed. + bool forced_door_closing( const tripoint &p, const ter_id &door_type, int bash_dmg ); + + /** Attempt to load first valid save (if any) in world */ + bool load( const std::string &world ); + + /** Returns true if the menu handled stuff and player shouldn't do anything else */ + bool npc_menu( npc &who ); + + // Handle phasing through walls, returns true if it handled the move + bool phasing_move( const tripoint &dest, bool via_ramp = false ); + // Regular movement. Returns false if it failed for any reason + bool walk_move( const tripoint &dest, bool via_ramp = false ); + void on_move_effects(); + private: + // Game-start procedures + bool load( const save_t &name ); // Load a player-specific save file + void load_master(); // Load the master data file, with factions &c +#if defined(__ANDROID__) + void load_shortcuts( std::istream &fin ); +#endif + bool start_game(); // Starts a new game in the active world + + //private save functions. + // returns false if saving failed for whatever reason + bool save_factions_missions_npcs(); + void reset_npc_dispositions(); + void serialize_master( std::ostream &fout ); + // returns false if saving failed for whatever reason + bool save_maps(); +#if defined(__ANDROID__) + void save_shortcuts( std::ostream &fout ); +#endif + // Data Initialization + void init_autosave(); // Initializes autosave parameters + void create_starting_npcs(); // Creates NPCs that start near you + // create vehicle nearby, for example; for a profession vehicle. + vehicle *place_vehicle_nearby( + const vproto_id &id, const point_abs_omt &origin, int min_distance, + int max_distance, const std::vector &omt_search_types = {} ); + // V Menu Functions and helpers: + void list_items_monsters(); // Called when you invoke the `V`-menu + + enum class vmenu_ret : int { + CHANGE_TAB, + QUIT, + FIRE, // Who knew, apparently you can do that in list_monsters + }; + + game::vmenu_ret list_items( const std::vector &item_list ); + std::vector find_nearby_items( int iRadius ); + void reset_item_list_state( const catacurses::window &window, int height, bool bRadiusSort ); + + game::vmenu_ret list_monsters( const std::vector &monster_list ); + + /** Check for dangerous stuff at dest_loc, return false if the player decides + not to step there */ + // Handle pushing during move, returns true if it handled the move + bool grabbed_move( const tripoint &dp ); + bool grabbed_veh_move( const tripoint &dp ); + bool grabbed_furn_move( const tripoint &dp ); + + void control_vehicle(); // Use vehicle controls '^' + void examine( const tripoint &p ); // Examine nearby terrain 'e' + void examine(); + + void pickup(); // Pickup nearby items 'g', min 0 + void pickup( const tripoint &p ); + void pickup_feet(); // Pick items at player position ',', min 1 + + void drop(); // Drop an item 'd' + void drop_in_direction(); // Drop w/ direction 'D' + + void butcher(); // Butcher a corpse 'B' + + void reload( item_location &loc, bool prompt = false, bool empty = true ); + public: + void reload_item(); // Reload an item + void reload_wielded( bool prompt = false ); + void reload_weapon( bool try_everything = true ); // Reload a wielded gun/tool 'r' + // Places the player at the specified point; hurts feet, lists items etc. + point place_player( const tripoint &dest ); + void place_player_overmap( const tripoint_abs_omt &om_dest ); + + unsigned int get_seed() const; + + /** If invoked, NPCs will be reloaded before next turn. */ + void set_npcs_dirty(); + /** If invoked, dead will be cleaned this turn. */ + void set_critter_died(); + void mon_info( const catacurses::window &, + int hor_padding = 0 ); // Prints a list of nearby monsters + void mon_info_update( ); //Update seen monsters information + void cleanup_dead(); // Delete any dead NPCs/monsters + bool is_dangerous_tile( const tripoint &dest_loc ) const; + std::vector get_dangerous_tile( const tripoint &dest_loc ) const; + bool prompt_dangerous_tile( const tripoint &dest_loc ) const; + private: + void wield(); + void wield( item_location loc ); + + void chat(); // Talk to a nearby NPC 'C' + + // Internal methods to show "look around" info + void print_fields_info( const tripoint &lp, const catacurses::window &w_look, int column, + int &line ); + void print_terrain_info( const tripoint &lp, const catacurses::window &w_look, + const std::string &area_name, int column, + int &line ); + void print_trap_info( const tripoint &lp, const catacurses::window &w_look, int column, + int &line ); + void print_creature_info( const Creature *creature, const catacurses::window &w_look, int column, + int &line, int last_line ); + void print_vehicle_info( const vehicle *veh, int veh_part, const catacurses::window &w_look, + int column, int &line, int last_line ); + void print_visibility_info( const catacurses::window &w_look, int column, int &line, + visibility_type visibility ); + void print_items_info( const tripoint &lp, const catacurses::window &w_look, int column, int &line, + int last_line ); + void print_graffiti_info( const tripoint &lp, const catacurses::window &w_look, int column, + int &line, int last_line ); + + input_context get_player_input( std::string &action ); + + // Map updating and monster spawning + void replace_stair_monsters(); + void update_stair_monsters(); + /** + * Shift all active monsters, the shift vector is the number of + * shifted submaps. Monsters that are outside of the reality bubble after + * shifting are despawned. + * Note on z-levels: this works with vertical shifts, but currently all + * monsters are despawned upon a vertical shift. + */ + void shift_monsters( const tripoint &shift ); + public: + /** + * Despawn a specific monster, it's stored on the overmap. Also removes + * it from the creature tracker. Keep in mind that any monster index may + * point to a different monster after calling this (or to no monster at all). + */ + void despawn_monster( monster &critter ); + + private: + void perhaps_add_random_npc(); + + // Routine loop functions, approximately in order of execution + void monmove(); // Monster movement + void overmap_npc_move(); // NPC overmap movement + void process_activity(); // Processes and enacts the player's activity + void handle_key_blocking_activity(); // Abort reading etc. + void open_consume_item_menu(); // Custom menu for consuming specific group of items + bool handle_action(); + bool try_get_right_click_action( action_id &act, const tripoint &mouse_target ); + bool try_get_left_click_action( action_id &act, const tripoint &mouse_target ); + + void item_action_menu(); // Displays item action menu + + bool is_game_over(); // Returns true if the player quit or died + void death_screen(); // Display our stats, "GAME OVER BOO HOO" + void draw_minimap(); // Draw the 5x5 minimap + public: + /** + * If there is a robot (that can be disabled), query the player + * and try to disable it. + * @return true if the robot has been disabled or a similar action has + * been done. false if the player did not choose any action and the function + * has effectively done nothing. + */ + bool disable_robot( const tripoint &p ); + // Draws the pixel minimap based on the player's current location + void draw_pixel_minimap( const catacurses::window &w ); + private: + + // int autosave_timeout(); // If autosave enabled, how long we should wait for user inaction before saving. + void autosave(); // automatic quicksaves - Performs some checks before calling quicksave() + public: + void quicksave(); // Saves the game without quitting + void disp_NPCs(); // Currently for debug use. Lists global NPCs. + + void list_missions(); // Listed current, completed and failed missions (mission_ui.cpp) + private: + void quickload(); // Loads the previously saved game if it exists + + // Input related + // Handles box showing items under mouse + bool handle_mouseview( input_context &ctxt, std::string &action ); + + // On-request draw functions + void display_faction_epilogues(); + void disp_NPC_epilogues(); // Display NPC endings + + /* Debug functions */ + // overlays flags (on / off) + std::map displaying_overlays{ + { ACTION_DISPLAY_SCENT, false }, + { ACTION_DISPLAY_SCENT_TYPE, false }, + { ACTION_DISPLAY_TEMPERATURE, false }, + { ACTION_DISPLAY_VEHICLE_AI, false }, + { ACTION_DISPLAY_VISIBILITY, false }, + { ACTION_DISPLAY_LIGHTING, false }, + { ACTION_DISPLAY_RADIATION, false }, + }; + void display_scent(); // Displays the scent map + void display_temperature(); // Displays temperature map + void display_vehicle_ai(); // Displays vehicle autopilot AI overlay + void display_visibility(); // Displays visibility map + void display_lighting(); // Displays lighting conditions heat map + void display_radiation(); // Displays radiation map + + Creature *is_hostile_within( int distance ); + + void move_save_to_graveyard(); + bool save_player_data(); + // ########################## DATA ################################ + private: + // May be a bit hacky, but it's probably better than the header spaghetti + pimpl map_ptr; + pimpl u_ptr; + pimpl liveview_ptr; + live_view &liveview; + pimpl scent_ptr; + pimpl timed_event_manager_ptr; + pimpl event_bus_ptr; + pimpl stats_tracker_ptr; + pimpl achievements_tracker_ptr; + pimpl kill_tracker_ptr; + pimpl memorial_logger_ptr; + pimpl spell_events_ptr; + + map &m; + avatar &u; + scent_map &scent; + const scenario *scen = nullptr; + + event_bus &events(); + stats_tracker &stats(); + timed_event_manager &timed_events; + achievements_tracker &achievements(); + memorial_logger &memorial(); + public: + + spell_events &spell_events_subscriber(); + + pimpl critter_tracker; + pimpl faction_manager_ptr; + + /** Used in main.cpp to determine what type of quit is being performed. */ + quit_status uquit; + /** True if the game has just started or loaded, else false. */ + bool new_game = false; + + std::vector coming_to_stairs; + int monstairz = 0; + + tripoint ter_view_p; + catacurses::window w_terrain; + catacurses::window w_overmap; + catacurses::window w_omlegend; + catacurses::window w_minimap; + catacurses::window w_pixel_minimap; + //only a pointer, can refer to w_messages_short or w_messages_long + + // View offset based on the driving speed (if any) + // that has been added to u.view_offset, + // Don't write to this directly, always use set_driving_view_offset + point driving_view_offset; + + bool debug_pathfinding = false; // show NPC pathfinding on overmap ui + + /* tile overlays */ + // Toggle all other overlays off and flip the given overlay on/off. + void display_toggle_overlay( action_id ); + // Get the state of an overlay (on/off). + bool display_overlay_state( action_id ); + /** Creature for which to display the visibility map */ + Creature *displaying_visibility_creature; + /** Type of lighting condition overlay to display */ + int displaying_lighting_condition = 0; + + bool show_panel_adm = false; + bool right_sidebar = false; + bool fullscreen = false; + bool was_fullscreen = false; + bool auto_travel_mode = false; + safe_mode_type safe_mode; + + //pixel minimap management + int pixel_minimap_option = 0; + int turnssincelastmon = 0; // needed for auto run mode + + weather_manager weather; + + int mostseen = 0; // # of mons seen last turn; if this increases, set safe_mode to SAFE_MODE_STOP + private: + shared_ptr_fast u_shared_ptr; + + catacurses::window w_terrain_ptr; + catacurses::window w_minimap_ptr; + + std::string sFilter; // a member so that it's remembered over time + std::string list_item_upvote; + std::string list_item_downvote; + + bool safe_mode_warning_logged = false; + bool bVMonsterLookFire = false; + character_id next_npc_id; + std::list> active_npc; + int next_mission_id = 0; + std::set follower_ids; // Keep track of follower NPC IDs + + std::chrono::seconds time_played_at_last_load; + std::chrono::time_point time_of_last_load; + int moves_since_last_save = 0; + time_t last_save_timestamp = 0; + + mutable std::array latest_lightlevels; + // remoteveh() cache + time_point remoteveh_cache_time; + vehicle *remoteveh_cache; + /** Has a NPC been spawned since last load? */ + bool npcs_dirty = false; + /** Has anything died in this turn and needs to be cleaned up? */ + bool critter_died = false; + /** Is this the first redraw since waiting (sleeping or activity) started */ + bool first_redraw_since_waiting_started = true; + /** Is Zone manager open or not - changes graphics of some zone tiles */ + bool zones_manager_open = false; + + std::unique_ptr gamemode; + + int user_action_counter = 0; // Times the user has input an action + + /** How far the tileset should be zoomed out, 16 is default. 32 is zoomed in by x2, 8 is zoomed out by x0.5 */ + int tileset_zoom = 0; + + /** Seed for all the random numbers that should have consistent randomness (weather). */ + unsigned int seed = 0; + + // Preview for auto move route + std::vector destination_preview; + + std::chrono::time_point last_mouse_edge_scroll; + tripoint last_mouse_edge_scroll_vector_terrain; + tripoint last_mouse_edge_scroll_vector_overmap; + std::pair mouse_edge_scrolling( input_context &ctxt, int speed, + const tripoint &last, bool iso ); + + weak_ptr_fast main_ui_adaptor; + + std::unique_ptr wait_popup; + public: + /** Used to implement mouse "edge scrolling". Returns a + * tripoint which is a vector of the resulting "move", i.e. + * (0, 0, 0) if the mouse is not at the edge of the screen, + * otherwise some (x, y, 0) depending on which edges are + * hit. + * This variant adjust scrolling speed according to zoom + * level, making it suitable when viewing the "terrain". + */ + tripoint mouse_edge_scrolling_terrain( input_context &ctxt ); + /** This variant is suitable for the overmap. */ + tripoint mouse_edge_scrolling_overmap( input_context &ctxt ); + + // called on map shifting + void shift_destination_preview( const point &delta ); + + /** + Checks if player is able to successfully climb to/from some terrain and not slip down + @param check_for_traps Used if needed to call trap function on player's location after slipping down + @return whether player has slipped down + */ + bool slip_down( bool check_for_traps = false ); +}; + +// Returns temperature modifier from direct heat radiation of nearby sources +// @param location Location affected by heat sources +// @param direct forces return of heat intensity (and not temperature modifier) of +// adjacent hottest heat source +int get_heat_radiation( const tripoint &location, bool direct ); +// Returns temperature modifier from hot air fields of given location +int get_convection_temperature( const tripoint &location ); + +namespace cata_event_dispatch +{ +// Constructs and dispatches an avatar movement event with the necessary parameters +// @param p The point the avatar moved from in absolute coordinates +// @param u The avatar (should have already moved to the new pos) +// @param m The map the avatar is moving on +void avatar_moves( const tripoint &old_abs_pos, const avatar &u, const map &m ); +} // namespace cata_event_dispatch + +#endif // CATA_SRC_GAME_H diff --git a/Src/json.cpp b/Src/json.cpp new file mode 100644 index 0000000000..ec21a481a9 --- /dev/null +++ b/Src/json.cpp @@ -0,0 +1,1942 @@ +#include "json.h" + +#include +#include +#include // IWYU pragma: keep +#include +#include +#include // strtoul: keep +#include // strcmp +#include +#include +#include +#include // ensure user's locale doesn't interfere with output +#include +#include // IWYU pragma: keep +#include +#include +#include + +#include "cata_utility.h" +#include "debug.h" +#include "string_formatter.h" + +extern bool test_mode; + +// JSON parsing and serialization tools for Cataclysm-DDA. +// For documentation, see the included header, json.h. + +static bool is_whitespace( char ch ) +{ + // These are all the valid whitespace characters allowed by RFC 4627. + return ( ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r' ); +} + +// for parsing \uxxxx escapes +static std::string utf16_to_utf8( uint32_t ch ) +{ + char out[5]; + char *buf = out; + static const unsigned char utf8FirstByte[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + int utf8Bytes; + if( ch < 0x80 ) { + utf8Bytes = 1; + } else if( ch < 0x800 ) { + utf8Bytes = 2; + } else if( ch < 0x10000 ) { + utf8Bytes = 3; + } else if( ch <= 0x10FFFF ) { + utf8Bytes = 4; + } else { + std::stringstream err; + err << "unknown unicode: " << ch; + throw std::runtime_error( err.str() ); + } + + buf += utf8Bytes; + switch( utf8Bytes ) { + case 4: + *--buf = ( ch | 0x80 ) & 0xBF; + ch >>= 6; + /* fallthrough */ + case 3: + *--buf = ( ch | 0x80 ) & 0xBF; + ch >>= 6; + /* fallthrough */ + case 2: + *--buf = ( ch | 0x80 ) & 0xBF; + ch >>= 6; + /* fallthrough */ + case 1: + *--buf = ch | utf8FirstByte[utf8Bytes]; + } + out[utf8Bytes] = '\0'; + return out; +} + +/* class JsonObject + * represents a JSON object, + * providing access to the underlying data. + */ +JsonObject::JsonObject( JsonIn &j ) +{ + jsin = &j; + start = jsin->tell(); + // cache the position of the value for each member + jsin->start_object(); + while( !jsin->end_object() ) { + std::string n = jsin->get_member_name(); + int p = jsin->tell(); + if( positions.count( n ) > 0 ) { + j.error( "duplicate entry in json object" ); + } + positions[n] = p; + jsin->skip_value(); + } + end_ = jsin->tell(); + final_separator = jsin->get_ate_separator(); +} + +void JsonObject::mark_visited( const std::string &name ) const +{ +#ifndef CATA_IN_TOOL + visited_members.emplace( name ); +#else + static_cast( name ); +#endif +} + +void JsonObject::report_unvisited() const +{ +#ifndef CATA_IN_TOOL + if( test_mode && report_unvisited_members && !reported_unvisited_members && + !std::uncaught_exception() ) { + reported_unvisited_members = true; + for( const std::pair &p : positions ) { + const std::string &name = p.first; + if( !visited_members.count( name ) && !string_starts_with( name, "//" ) && + name != "blueprint" ) { + try { + throw_error( string_format( "Failed to visit member %s in JsonObject", name ), name ); + } catch( const JsonError &e ) { + debugmsg( "\n%s", e.what() ); + } + } + } + } +#endif +} + +void JsonObject::finish() +{ + report_unvisited(); + if( jsin && jsin->good() ) { + jsin->seek( end_ ); + jsin->set_ate_separator( final_separator ); + } +} + +size_t JsonObject::size() const +{ + return positions.size(); +} +bool JsonObject::empty() const +{ + return positions.empty(); +} + +void JsonObject::allow_omitted_members() const +{ +#ifndef CATA_IN_TOOL + report_unvisited_members = false; +#endif +} + +int JsonObject::verify_position( const std::string &name, + const bool throw_exception ) const +{ + if( !jsin ) { + if( throw_exception ) { + throw JsonError( std::string( "member lookup on empty object: " ) + name ); + } + // 0 is always the opening brace, + // so it will never indicate a valid member position + return 0; + } + const auto iter = positions.find( name ); + if( iter == positions.end() ) { + if( throw_exception ) { + jsin->seek( start ); + jsin->error( "member not found: " + name ); + } + // 0 is always the opening brace, + // so it will never indicate a valid member position + return 0; + } + return iter->second; +} + +bool JsonObject::has_member( const std::string &name ) const +{ + return positions.count( name ) > 0; +} + +std::string JsonObject::line_number() const +{ + jsin->seek( start ); + return jsin->line_number(); +} + +std::string JsonObject::str() const +{ + // If we're getting the string form, we might be re-parsing later, so don't + // complain about unvisited members. + allow_omitted_members(); + + if( jsin && end_ >= start ) { + return jsin->substr( start, end_ - start ); + } else { + return "{}"; + } +} + +void JsonObject::throw_error( const std::string &err, const std::string &name ) const +{ + if( !jsin ) { + throw JsonError( err ); + } + jsin->seek( verify_position( name, false ) ); + jsin->error( err ); +} + +void JsonArray::throw_error( const std::string &err ) +{ + if( !jsin ) { + throw JsonError( err ); + } + jsin->error( err ); +} + +void JsonArray::throw_error( const std::string &err, int idx ) +{ + if( !jsin ) { + throw JsonError( err ); + } + if( idx >= 0 && size_t( idx ) < positions.size() ) { + jsin->seek( positions[idx] ); + } + jsin->error( err ); +} + +void JsonObject::throw_error( const std::string &err ) const +{ + if( !jsin ) { + throw JsonError( err ); + } + jsin->error( err ); +} + +JsonIn *JsonObject::get_raw( const std::string &name ) const +{ + int pos = verify_position( name ); + mark_visited( name ); + jsin->seek( pos ); + return jsin; +} + +/* returning values by name */ + +bool JsonObject::get_bool( const std::string &name ) const +{ + return get_member( name ).get_bool(); +} + +bool JsonObject::get_bool( const std::string &name, const bool fallback ) const +{ + int pos = verify_position( name, false ); + if( !pos ) { + return fallback; + } + mark_visited( name ); + jsin->seek( pos ); + return jsin->get_bool(); +} + +int JsonObject::get_int( const std::string &name ) const +{ + return get_member( name ).get_int(); +} + +int JsonObject::get_int( const std::string &name, const int fallback ) const +{ + int pos = verify_position( name, false ); + if( !pos ) { + return fallback; + } + mark_visited( name ); + jsin->seek( pos ); + return jsin->get_int(); +} + +double JsonObject::get_float( const std::string &name ) const +{ + return get_member( name ).get_float(); +} + +double JsonObject::get_float( const std::string &name, const double fallback ) const +{ + int pos = verify_position( name, false ); + if( !pos ) { + return fallback; + } + mark_visited( name ); + jsin->seek( pos ); + return jsin->get_float(); +} + +std::string JsonObject::get_string( const std::string &name ) const +{ + return get_member( name ).get_string(); +} + +std::string JsonObject::get_string( const std::string &name, const std::string &fallback ) const +{ + int pos = verify_position( name, false ); + if( !pos ) { + return fallback; + } + mark_visited( name ); + jsin->seek( pos ); + return jsin->get_string(); +} + +/* returning containers by name */ + +JsonArray JsonObject::get_array( const std::string &name ) const +{ + int pos = verify_position( name, false ); + if( !pos ) { + return JsonArray(); + } + mark_visited( name ); + jsin->seek( pos ); + return JsonArray( *jsin ); +} + +std::vector JsonObject::get_int_array( const std::string &name ) const +{ + std::vector ret; + for( const int entry : get_array( name ) ) { + ret.push_back( entry ); + } + return ret; +} + +std::vector JsonObject::get_string_array( const std::string &name ) const +{ + std::vector ret; + for( const std::string entry : get_array( name ) ) { + ret.push_back( entry ); + } + return ret; +} + +JsonObject JsonObject::get_object( const std::string &name ) const +{ + int pos = verify_position( name, false ); + if( !pos ) { + return JsonObject(); + } + mark_visited( name ); + jsin->seek( pos ); + return jsin->get_object(); +} + +/* non-fatal member existence and type testing */ + +bool JsonObject::has_null( const std::string &name ) const +{ + int pos = verify_position( name, false ); + if( !pos ) { + return false; + } + mark_visited( name ); + jsin->seek( pos ); + return jsin->test_null(); +} + +bool JsonObject::has_bool( const std::string &name ) const +{ + int pos = verify_position( name, false ); + if( !pos ) { + return false; + } + jsin->seek( pos ); + return jsin->test_bool(); +} + +bool JsonObject::has_number( const std::string &name ) const +{ + int pos = verify_position( name, false ); + if( !pos ) { + return false; + } + jsin->seek( pos ); + return jsin->test_number(); +} + +bool JsonObject::has_string( const std::string &name ) const +{ + int pos = verify_position( name, false ); + if( !pos ) { + return false; + } + jsin->seek( pos ); + return jsin->test_string(); +} + +bool JsonObject::has_array( const std::string &name ) const +{ + int pos = verify_position( name, false ); + if( !pos ) { + return false; + } + jsin->seek( pos ); + return jsin->test_array(); +} + +bool JsonObject::has_object( const std::string &name ) const +{ + int pos = verify_position( name, false ); + if( !pos ) { + return false; + } + jsin->seek( pos ); + return jsin->test_object(); +} + +/* class JsonArray + * represents a JSON array, + * providing access to the underlying data. + */ +JsonArray::JsonArray( JsonIn &j ) +{ + jsin = &j; + start = jsin->tell(); + index = 0; + // cache the position of each element + jsin->start_array(); + while( !jsin->end_array() ) { + positions.push_back( jsin->tell() ); + jsin->skip_value(); + } + end_ = jsin->tell(); + final_separator = jsin->get_ate_separator(); +} + +JsonArray::JsonArray( const JsonArray &ja ) +{ + jsin = ja.jsin; + start = ja.start; + index = 0; + positions = ja.positions; + end_ = ja.end_; + final_separator = ja.final_separator; +} + +JsonArray &JsonArray::operator=( const JsonArray &ja ) +{ + jsin = ja.jsin; + start = ja.start; + index = 0; + positions = ja.positions; + end_ = ja.end_; + final_separator = ja.final_separator; + + return *this; +} + +void JsonArray::finish() +{ + if( jsin && jsin->good() ) { + jsin->seek( end_ ); + jsin->set_ate_separator( final_separator ); + } +} + +bool JsonArray::has_more() const +{ + return index < positions.size(); +} +size_t JsonArray::size() const +{ + return positions.size(); +} +bool JsonArray::empty() +{ + return positions.empty(); +} + +std::string JsonArray::str() +{ + if( jsin ) { + return jsin->substr( start, end_ - start ); + } else { + return "[]"; + } +} + +void JsonArray::verify_index( const size_t i ) const +{ + if( !jsin ) { + throw JsonError( "tried to access empty array." ); + } else if( i >= positions.size() ) { + jsin->seek( start ); + std::stringstream err; + err << "bad index value: " << i; + jsin->error( err.str() ); + } +} + +/* iterative access */ + +bool JsonArray::next_bool() +{ + verify_index( index ); + jsin->seek( positions[index++] ); + return jsin->get_bool(); +} + +int JsonArray::next_int() +{ + verify_index( index ); + jsin->seek( positions[index++] ); + return jsin->get_int(); +} + +double JsonArray::next_float() +{ + verify_index( index ); + jsin->seek( positions[index++] ); + return jsin->get_float(); +} + +std::string JsonArray::next_string() +{ + verify_index( index ); + jsin->seek( positions[index++] ); + return jsin->get_string(); +} + +JsonArray JsonArray::next_array() +{ + verify_index( index ); + jsin->seek( positions[index++] ); + return jsin->get_array(); +} + +JsonObject JsonArray::next_object() +{ + verify_index( index ); + jsin->seek( positions[index++] ); + return jsin->get_object(); +} + +void JsonArray::skip_value() +{ + verify_index( index ); + ++index; +} + +/* static access */ + +bool JsonArray::get_bool( const size_t i ) const +{ + verify_index( i ); + jsin->seek( positions[i] ); + return jsin->get_bool(); +} + +int JsonArray::get_int( const size_t i ) const +{ + verify_index( i ); + jsin->seek( positions[i] ); + return jsin->get_int(); +} + +double JsonArray::get_float( const size_t i ) const +{ + verify_index( i ); + jsin->seek( positions[i] ); + return jsin->get_float(); +} + +std::string JsonArray::get_string( const size_t i ) const +{ + verify_index( i ); + jsin->seek( positions[i] ); + return jsin->get_string(); +} + +JsonArray JsonArray::get_array( const size_t i ) const +{ + verify_index( i ); + jsin->seek( positions[i] ); + return jsin->get_array(); +} + +JsonObject JsonArray::get_object( const size_t i ) const +{ + verify_index( i ); + jsin->seek( positions[i] ); + return jsin->get_object(); +} + +/* iterative type checking */ + +bool JsonArray::test_null() const +{ + if( !has_more() ) { + return false; + } + jsin->seek( positions[index] ); + return jsin->test_null(); +} + +bool JsonArray::test_bool() const +{ + if( !has_more() ) { + return false; + } + jsin->seek( positions[index] ); + return jsin->test_bool(); +} + +bool JsonArray::test_number() const +{ + if( !has_more() ) { + return false; + } + jsin->seek( positions[index] ); + return jsin->test_number(); +} + +bool JsonArray::test_string() const +{ + if( !has_more() ) { + return false; + } + jsin->seek( positions[index] ); + return jsin->test_string(); +} + +bool JsonArray::test_bitset() const +{ + if( !has_more() ) { + return false; + } + jsin->seek( positions[index] ); + return jsin->test_bitset(); +} + +bool JsonArray::test_array() const +{ + if( !has_more() ) { + return false; + } + jsin->seek( positions[index] ); + return jsin->test_array(); +} + +bool JsonArray::test_object() const +{ + if( !has_more() ) { + return false; + } + jsin->seek( positions[index] ); + return jsin->test_object(); +} + +/* random-access type checking */ + +bool JsonArray::has_null( const size_t i ) const +{ + verify_index( i ); + jsin->seek( positions[i] ); + return jsin->test_null(); +} + +bool JsonArray::has_bool( const size_t i ) const +{ + verify_index( i ); + jsin->seek( positions[i] ); + return jsin->test_bool(); +} + +bool JsonArray::has_number( const size_t i ) const +{ + verify_index( i ); + jsin->seek( positions[i] ); + return jsin->test_number(); +} + +bool JsonArray::has_string( const size_t i ) const +{ + verify_index( i ); + jsin->seek( positions[i] ); + return jsin->test_string(); +} + +bool JsonArray::has_array( const size_t i ) const +{ + verify_index( i ); + jsin->seek( positions[i] ); + return jsin->test_array(); +} + +bool JsonArray::has_object( const size_t i ) const +{ + verify_index( i ); + jsin->seek( positions[i] ); + return jsin->test_object(); +} + +void add_array_to_set( std::set &s, const JsonObject &json, const std::string &name ) +{ + for( const std::string line : json.get_array( name ) ) { + s.insert( line ); + } +} + +int JsonIn::tell() +{ + return stream->tellg(); +} +char JsonIn::peek() +{ + return static_cast( stream->peek() ); +} +bool JsonIn::good() +{ + return stream->good(); +} + +void JsonIn::seek( int pos ) +{ + stream->clear(); + stream->seekg( pos ); + ate_separator = false; +} + +void JsonIn::eat_whitespace() +{ + while( is_whitespace( peek() ) ) { + stream->get(); + } +} + +void JsonIn::uneat_whitespace() +{ + while( tell() > 0 ) { + stream->seekg( -1, std::istream::cur ); + if( !is_whitespace( peek() ) ) { + break; + } + } +} + +void JsonIn::end_value() +{ + ate_separator = false; + skip_separator(); +} + +void JsonIn::skip_member() +{ + skip_string(); + skip_pair_separator(); + skip_value(); +} + +void JsonIn::skip_separator() +{ + eat_whitespace(); + signed char ch = peek(); + if( ch == ',' ) { + if( ate_separator ) { + error( "duplicate separator" ); + } + stream->get(); + ate_separator = true; + } else if( ch == ']' || ch == '}' || ch == ':' ) { + // okay + if( ate_separator ) { + std::stringstream err; + err << "separator should not be found before '" << ch << "'"; + uneat_whitespace(); + error( err.str() ); + } + ate_separator = false; + } else if( ch == EOF ) { + // that's okay too... probably + if( ate_separator ) { + uneat_whitespace(); + error( "separator at end of file not strictly allowed" ); + } + ate_separator = false; + } else { + // not okay >:( + uneat_whitespace(); + error( "missing separator", 1 ); + } +} + +void JsonIn::skip_pair_separator() +{ + char ch; + eat_whitespace(); + stream->get( ch ); + if( ch != ':' ) { + std::stringstream err; + err << "expected pair separator ':', not '" << ch << "'"; + error( err.str(), -1 ); + } else if( ate_separator ) { + error( "duplicate separator not strictly allowed", -1 ); + } + ate_separator = true; +} + +void JsonIn::skip_string() +{ + char ch; + eat_whitespace(); + stream->get( ch ); + if( ch != '"' ) { + std::stringstream err; + err << "expecting string but found '" << ch << "'"; + error( err.str(), -1 ); + } + while( stream->good() ) { + stream->get( ch ); + if( ch == '\\' ) { + stream->get( ch ); + continue; + } else if( ch == '"' ) { + break; + } else if( ch == '\r' || ch == '\n' ) { + error( "string not closed before end of line", -1 ); + } + } + end_value(); +} + +void JsonIn::skip_value() +{ + eat_whitespace(); + char ch = peek(); + // it's either a string '"' + if( ch == '"' ) { + skip_string(); + // or an object '{' + } else if( ch == '{' ) { + skip_object(); + // or an array '[' + } else if( ch == '[' ) { + skip_array(); + // or a number (-0123456789) + } else if( ch == '-' || ( ch >= '0' && ch <= '9' ) ) { + skip_number(); + // or "true", "false" or "null" + } else if( ch == 't' ) { + skip_true(); + } else if( ch == 'f' ) { + skip_false(); + } else if( ch == 'n' ) { + skip_null(); + // or an error. + } else { + std::stringstream err; + err << "expected JSON value but got '" << ch << "'"; + error( err.str() ); + } + // skip_* end value automatically +} + +void JsonIn::skip_object() +{ + start_object(); + while( !end_object() ) { + skip_member(); + } + // end_value called by end_object +} + +void JsonIn::skip_array() +{ + start_array(); + while( !end_array() ) { + skip_value(); + } + // end_value called by end_array +} + +void JsonIn::skip_true() +{ + char text[5]; + eat_whitespace(); + stream->get( text, 5 ); + if( strcmp( text, "true" ) != 0 ) { + std::stringstream err; + err << R"(expected "true", but found ")" << text << "\""; + error( err.str(), -4 ); + } + end_value(); +} + +void JsonIn::skip_false() +{ + char text[6]; + eat_whitespace(); + stream->get( text, 6 ); + if( strcmp( text, "false" ) != 0 ) { + std::stringstream err; + err << R"(expected "false", but found ")" << text << "\""; + error( err.str(), -5 ); + } + end_value(); +} + +void JsonIn::skip_null() +{ + char text[5]; + eat_whitespace(); + stream->get( text, 5 ); + if( strcmp( text, "null" ) != 0 ) { + std::stringstream err; + err << R"(expected "null", but found ")" << text << "\""; + error( err.str(), -4 ); + } + end_value(); +} + +void JsonIn::skip_number() +{ + char ch; + eat_whitespace(); + // skip all of (+-0123456789.eE) + while( stream->good() ) { + stream->get( ch ); + if( ch != '+' && ch != '-' && ( ch < '0' || ch > '9' ) && + ch != 'e' && ch != 'E' && ch != '.' ) { + stream->unget(); + break; + } + } + end_value(); +} + +std::string JsonIn::get_member_name() +{ + std::string s = get_string(); + skip_pair_separator(); + return s; +} + +std::string JsonIn::get_string() +{ + std::string s; + char ch; + bool backslash = false; + char unihex[5] = "0000"; + eat_whitespace(); + int startpos = tell(); + // the first character had better be a '"' + stream->get( ch ); + if( ch != '"' ) { + std::stringstream err; + err << "expecting string but got '" << ch << "'"; + error( err.str(), -1 ); + } + // add chars to the string, one at a time, converting: + // \", \\, \/, \b, \f, \n, \r, \t and \uxxxx according to JSON spec. + while( stream->good() ) { + stream->get( ch ); + if( ch == '\\' ) { + if( backslash ) { + s += '\\'; + backslash = false; + } else { + backslash = true; + continue; + } + } else if( backslash ) { + backslash = false; + if( ch == '"' ) { + s += '"'; + } else if( ch == '/' ) { + s += '/'; + } else if( ch == 'b' ) { + s += '\b'; + } else if( ch == 'f' ) { + s += '\f'; + } else if( ch == 'n' ) { + s += '\n'; + } else if( ch == 'r' ) { + s += '\r'; + } else if( ch == 't' ) { + s += '\t'; + } else if( ch == 'u' ) { + // get the next four characters as hexadecimal + stream->get( unihex, 5 ); + // insert the appropriate unicode character in utf8 + // TODO: verify that unihex is in fact 4 hex digits. + char **endptr = nullptr; + uint32_t u = static_cast( strtoul( unihex, endptr, 16 ) ); + try { + s += utf16_to_utf8( u ); + } catch( const std::exception &err ) { + error( err.what() ); + } + } else { + // for anything else, just add the character, i suppose + s += ch; + } + } else if( ch == '"' ) { + // end of the string + end_value(); + return s; + } else if( ch == '\r' || ch == '\n' ) { + error( "reached end of line without closing string", -1 ); + } else if( static_cast( ch ) < 0x20 ) { + error( "invalid character inside string", -1 ); + } else { + s += ch; + } + } + // if we get to here, probably hit a premature EOF? + if( stream->eof() ) { + stream->clear(); + seek( startpos ); + error( "couldn't find end of string, reached EOF." ); + } else if( stream->fail() ) { + throw JsonError( "stream failure while reading string." ); + } + throw JsonError( "something went wrong D:" ); +} + +// These functions get -INT_MIN and -INT64_MIN while very carefully avoiding any overflow. +constexpr static uint64_t neg_INT_MIN() +{ + static_assert( sizeof( int ) <= sizeof( int64_t ), + "neg_INT_MIN() assumed sizeof( int ) <= sizeof( int64_t )" ); + constexpr int x = std::numeric_limits::min() + std::numeric_limits::max(); + static_assert( x >= 0 || x + std::numeric_limits::max() >= 0, + "neg_INT_MIN assumed INT_MIN + INT_MAX >= -INT_MAX" ); + if( x < 0 ) { + return static_cast( std::numeric_limits::max() ) + static_cast( -x ); + } else { + return static_cast( std::numeric_limits::max() ) - static_cast( x ); + } +} +constexpr static uint64_t neg_INT64_MIN() +{ + constexpr int64_t x = std::numeric_limits::min() + std::numeric_limits::max(); + static_assert( x >= 0 || x + std::numeric_limits::max() >= 0, + "neg_INT64_MIN assumed INT64_MIN + INT64_MAX >= -INT64_MAX" ); + if( x < 0 ) { + return static_cast( std::numeric_limits::max() ) + static_cast( -x ); + } else { + return static_cast( std::numeric_limits::max() ) - static_cast( x ); + } +} + +number_sci_notation JsonIn::get_any_int() +{ + number_sci_notation n = get_any_number(); + if( n.exp < 0 ) { + error( "Integers cannot have a decimal point or negative order of magnitude." ); + } + // Manually apply scientific notation, since std::pow converts to double under the hood. + for( int64_t i = 0; i < n.exp; i++ ) { + if( n.number > std::numeric_limits::max() / 10ULL ) { + error( "Specified order of magnitude too large -- encountered overflow applying it." ); + } + n.number *= 10ULL; + } + n.exp = 0; + return n; +} + +int JsonIn::get_int() +{ + static_assert( sizeof( int ) <= sizeof( int64_t ), + "JsonIn::get_int() assumed sizeof( int ) <= sizeof( int64_t )" ); + number_sci_notation n = get_any_int(); + if( !n.negative && n.number > static_cast( std::numeric_limits::max() ) ) { + error( "Found a number greater than " + std::to_string( std::numeric_limits::max() ) + + " which is unsupported in this context." ); + } else if( n.negative && n.number > neg_INT_MIN() ) { + error( "Found a number less than " + std::to_string( std::numeric_limits::min() ) + + " which is unsupported in this context." ); + } + if( n.negative ) { + static_assert( neg_INT_MIN() <= static_cast( std::numeric_limits::max() ) + || neg_INT_MIN() - static_cast( std::numeric_limits::max() ) + <= static_cast( std::numeric_limits::max() ), + "JsonIn::get_int() assumed -INT_MIN - INT_MAX <= INT_MAX" ); + if( n.number > static_cast( std::numeric_limits::max() ) ) { + const uint64_t x = n.number - static_cast( std::numeric_limits::max() ); + return -std::numeric_limits::max() - static_cast( x ); + } else { + return -static_cast( n.number ); + } + } else { + return static_cast( n.number ); + } +} + +unsigned int JsonIn::get_uint() +{ + number_sci_notation n = get_any_int(); + if( n.number > std::numeric_limits::max() ) { + error( "Found a number greater than " + + std::to_string( std::numeric_limits::max() ) + + " which is unsupported in this context." ); + } + if( n.negative ) { + error( "Unsigned integers cannot have a negative sign." ); + } + return static_cast( n.number ); +} + +int64_t JsonIn::get_int64() +{ + number_sci_notation n = get_any_int(); + if( !n.negative && n.number > static_cast( std::numeric_limits::max() ) ) { + error( "Signed integers greater than " + + std::to_string( std::numeric_limits::max() ) + " not supported." ); + } else if( n.negative && n.number > neg_INT64_MIN() ) { + error( "Integers less than " + + std::to_string( std::numeric_limits::min() ) + " not supported." ); + } + if( n.negative ) { + static_assert( neg_INT64_MIN() <= static_cast( std::numeric_limits::max() ) + || neg_INT64_MIN() - static_cast( std::numeric_limits::max() ) + <= static_cast( std::numeric_limits::max() ), + "JsonIn::get_int64() assumed -INT64_MIN - INT64_MAX <= INT64_MAX" ); + if( n.number > static_cast( std::numeric_limits::max() ) ) { + const uint64_t x = n.number - static_cast( std::numeric_limits::max() ); + return -std::numeric_limits::max() - static_cast( x ); + } else { + return -static_cast( n.number ); + } + } else { + return static_cast( n.number ); + } +} + +uint64_t JsonIn::get_uint64() +{ + number_sci_notation n = get_any_int(); + if( n.negative ) { + error( "Unsigned integers cannot have a negative sign." ); + } + return n.number; +} + +double JsonIn::get_float() +{ + number_sci_notation n = get_any_number(); + return n.number * std::pow( 10.0f, n.exp ) * ( n.negative ? -1.f : 1.f ); +} + +number_sci_notation JsonIn::get_any_number() +{ + // this could maybe be prettier? + char ch; + number_sci_notation ret; + int mod_e = 0; + eat_whitespace(); + stream->get( ch ); + if( ( ret.negative = ch == '-' ) ) { + stream->get( ch ); + } else if( ch != '.' && ( ch < '0' || ch > '9' ) ) { + // not a valid float + std::stringstream err; + err << "expecting number but found '" << ch << "'"; + error( err.str(), -1 ); + } + if( ch == '0' ) { + // allow a single leading zero in front of a '.' or 'e'/'E' + stream->get( ch ); + if( ch >= '0' && ch <= '9' ) { + error( "leading zeros not strictly allowed", -1 ); + } + } + while( ch >= '0' && ch <= '9' ) { + ret.number *= 10; + ret.number += ( ch - '0' ); + stream->get( ch ); + } + if( ch == '.' ) { + stream->get( ch ); + while( ch >= '0' && ch <= '9' ) { + ret.number *= 10; + ret.number += ( ch - '0' ); + mod_e -= 1; + stream->get( ch ); + } + } + if( ch == 'e' || ch == 'E' ) { + stream->get( ch ); + bool neg; + if( ( neg = ch == '-' ) ) { + stream->get( ch ); + } else if( ch == '+' ) { + stream->get( ch ); + } + while( ch >= '0' && ch <= '9' ) { + ret.exp *= 10; + ret.exp += ( ch - '0' ); + stream->get( ch ); + } + if( neg ) { + ret.exp *= -1; + } + } + // unget the final non-number character (probably a separator) + stream->unget(); + end_value(); + ret.exp += mod_e; + return ret; +} + +bool JsonIn::get_bool() +{ + char ch; + char text[5]; + std::stringstream err; + eat_whitespace(); + stream->get( ch ); + if( ch == 't' ) { + stream->get( text, 4 ); + if( strcmp( text, "rue" ) == 0 ) { + end_value(); + return true; + } else { + err << R"(not a boolean. expected "true", but got ")"; + err << ch << text << "\""; + error( err.str(), -4 ); + } + } else if( ch == 'f' ) { + stream->get( text, 5 ); + if( strcmp( text, "alse" ) == 0 ) { + end_value(); + return false; + } else { + err << R"(not a boolean. expected "false", but got ")"; + err << ch << text << "\""; + error( err.str(), -5 ); + } + } + err << "not a boolean value! expected 't' or 'f' but got '" << ch << "'"; + error( err.str(), -1 ); +} + +JsonObject JsonIn::get_object() +{ + return JsonObject( *this ); +} +JsonArray JsonIn::get_array() +{ + return JsonArray( *this ); +} + +void JsonIn::start_array() +{ + eat_whitespace(); + if( peek() == '[' ) { + stream->get(); + ate_separator = false; + return; + } else { + // expecting an array, so this is an error + std::stringstream err; + err << "tried to start array, but found '"; + err << peek() << "', not '['"; + error( err.str() ); + } +} + +bool JsonIn::end_array() +{ + eat_whitespace(); + if( peek() == ']' ) { + if( ate_separator ) { + uneat_whitespace(); + error( "separator not strictly allowed at end of array" ); + } + stream->get(); + end_value(); + return true; + } else { + // not the end yet, so just return false? + return false; + } +} + +void JsonIn::start_object() +{ + eat_whitespace(); + if( peek() == '{' ) { + stream->get(); + ate_separator = false; // not that we want to + return; + } else { + // expecting an object, so fail loudly + std::stringstream err; + err << "tried to start object, but found '"; + err << peek() << "', not '{'"; + error( err.str() ); + } +} + +bool JsonIn::end_object() +{ + eat_whitespace(); + if( peek() == '}' ) { + if( ate_separator ) { + uneat_whitespace(); + error( "separator not strictly allowed at end of object" ); + } + stream->get(); + end_value(); + return true; + } else { + // not the end yet, so just return false? + return false; + } +} + +bool JsonIn::test_null() +{ + eat_whitespace(); + return peek() == 'n'; +} + +bool JsonIn::test_bool() +{ + eat_whitespace(); + const char ch = peek(); + return ch == 't' || ch == 'f'; +} + +bool JsonIn::test_number() +{ + eat_whitespace(); + const char ch = peek(); + return ch == '-' || ch == '+' || ch == '.' || ( ch >= '0' && ch <= '9' ); +} + +bool JsonIn::test_string() +{ + eat_whitespace(); + return peek() == '"'; +} + +bool JsonIn::test_bitset() +{ + eat_whitespace(); + return peek() == '"'; +} + +bool JsonIn::test_array() +{ + eat_whitespace(); + return peek() == '['; +} + +bool JsonIn::test_object() +{ + eat_whitespace(); + return peek() == '{'; +} + +/* non-fatal value setting by reference */ + +bool JsonIn::read( bool &b, bool throw_on_error ) +{ + if( !test_bool() ) { + return error_or_false( throw_on_error, "Expected bool" ); + } + b = get_bool(); + return true; +} + +bool JsonIn::read( char &c, bool throw_on_error ) +{ + if( !test_number() ) { + return error_or_false( throw_on_error, "Expected number" ); + } + c = get_int(); + return true; +} + +bool JsonIn::read( signed char &c, bool throw_on_error ) +{ + if( !test_number() ) { + return error_or_false( throw_on_error, "Expected number" ); + } + // TODO: test for overflow + c = get_int(); + return true; +} + +bool JsonIn::read( unsigned char &c, bool throw_on_error ) +{ + if( !test_number() ) { + return error_or_false( throw_on_error, "Expected number" ); + } + // TODO: test for overflow + c = get_int(); + return true; +} + +bool JsonIn::read( short unsigned int &s, bool throw_on_error ) +{ + if( !test_number() ) { + return error_or_false( throw_on_error, "Expected number" ); + } + // TODO: test for overflow + s = get_int(); + return true; +} + +bool JsonIn::read( short int &s, bool throw_on_error ) +{ + if( !test_number() ) { + return error_or_false( throw_on_error, "Expected number" ); + } + // TODO: test for overflow + s = get_int(); + return true; +} + +bool JsonIn::read( int &i, bool throw_on_error ) +{ + if( !test_number() ) { + return error_or_false( throw_on_error, "Expected number" ); + } + i = get_int(); + return true; +} + +bool JsonIn::read( std::int64_t &i, bool throw_on_error ) +{ + if( !test_number() ) { + return error_or_false( throw_on_error, "Expected number" ); + } + i = get_int64(); + return true; +} + +bool JsonIn::read( std::uint64_t &i, bool throw_on_error ) +{ + if( !test_number() ) { + return error_or_false( throw_on_error, "Expected number" ); + } + i = get_uint64(); + return true; +} + +bool JsonIn::read( unsigned int &u, bool throw_on_error ) +{ + if( !test_number() ) { + return error_or_false( throw_on_error, "Expected number" ); + } + u = get_uint(); + return true; +} + +bool JsonIn::read( float &f, bool throw_on_error ) +{ + if( !test_number() ) { + return error_or_false( throw_on_error, "Expected number" ); + } + f = get_float(); + return true; +} + +bool JsonIn::read( double &d, bool throw_on_error ) +{ + if( !test_number() ) { + return error_or_false( throw_on_error, "Expected number" ); + } + d = get_float(); + return true; +} + +bool JsonIn::read( std::string &s, bool throw_on_error ) +{ + if( !test_string() ) { + return error_or_false( throw_on_error, "Expected string" ); + } + s = get_string(); + return true; +} + +template +bool JsonIn::read( std::bitset &b, bool throw_on_error ) +{ + if( !test_bitset() ) { + return error_or_false( throw_on_error, "Expected bitset" ); + } + std::string tmp_string = get_string(); + if( tmp_string.length() > N ) { + // If the loaded string contains more bits than expected, skip the most significant bits + tmp_string.erase( 0, tmp_string.length() - N ); + } + b = std::bitset ( tmp_string ); + return true; +} + +bool JsonIn::read( JsonDeserializer &j, bool throw_on_error ) +{ + // can't know what type of json object it will deserialize from, + // so just try to deserialize, catching any error. + // TODO: non-verbose flag for JsonIn errors so try/catch is faster here + try { + j.deserialize( *this ); + return true; + } catch( const JsonError & ) { + if( throw_on_error ) { + throw; + } + return false; + } +} + +/* error display */ + +// WARNING: for occasional use only. +std::string JsonIn::line_number( int offset_modifier ) +{ + if( !stream || stream->fail() ) { + return "???"; + } + if( stream->eof() ) { + return "EOF"; + } // else stream is fine + int pos = tell(); + int line = 1; + int offset = 1; + char ch; + seek( 0 ); + for( int i = 0; i < pos; ++i ) { + stream->get( ch ); + if( ch == '\r' ) { + offset = 1; + ++line; + if( peek() == '\n' ) { + stream->get(); + ++i; + } + } else if( ch == '\n' ) { + offset = 1; + ++line; + } else { + ++offset; + } + } + std::stringstream ret; + ret << "line " << line << ":" << ( offset + offset_modifier ); + return ret.str(); +} + +void JsonIn::error( const std::string &message, int offset ) +{ + std::ostringstream err; + err << line_number( offset ) << ": " << message; + // if we can't get more info from the stream don't try + if( !stream->good() ) { + throw JsonError( err.str() ); + } + // also print surrounding few lines of context, if not too large + err << "\n\n"; + stream->seekg( offset, std::istream::cur ); + size_t pos = tell(); + rewind( 3, 240 ); + size_t startpos = tell(); + std::string buffer( pos - startpos, '\0' ); + stream->read( &buffer[0], pos - startpos ); + auto it = buffer.begin(); + for( ; it < buffer.end() && ( *it == '\r' || *it == '\n' ); ++it ) { + // skip starting newlines + } + for( ; it < buffer.end(); ++it ) { + if( *it == '\r' ) { + err << '\n'; + if( it + 1 < buffer.end() && *( it + 1 ) == '\n' ) { + ++it; + } + } else { + err << *it; + } + } + if( !is_whitespace( peek() ) ) { + err << peek(); + } + // display a pointer to the position + rewind( 1, 240 ); + startpos = tell(); + err << '\n'; + if( pos > startpos ) { + err << std::string( pos - startpos - 1, ' ' ); + } + err << "^\n"; + seek( pos ); + // if that wasn't the end of the line, continue underneath pointer + char ch = stream->get(); + if( ch == '\r' ) { + if( peek() == '\n' ) { + stream->get(); + } + } else if( ch == '\n' ) { + // pass + } else if( peek() != '\r' && peek() != '\n' ) { + for( size_t i = 0; i < pos - startpos; ++i ) { + err << ' '; + } + } + // print the next couple lines as well + int line_count = 0; + for( int i = 0; line_count < 3 && stream->good() && i < 240; ++i ) { + stream->get( ch ); + if( !stream->good() ) { + break; + } + if( ch == '\r' ) { + ch = '\n'; + ++line_count; + if( stream->peek() == '\n' ) { + stream->get( ch ); + } + } else if( ch == '\n' ) { + ++line_count; + } + err << ch; + } + std::string msg = err.str(); + if( !msg.empty() && msg.back() != '\n' ) { + msg.push_back( '\n' ); + } + throw JsonError( msg ); +} + +bool JsonIn::error_or_false( bool throw_, const std::string &message, int offset ) +{ + if( throw_ ) { + error( message, offset ); + } + return false; +} + +void JsonIn::rewind( int max_lines, int max_chars ) +{ + if( max_lines < 0 && max_chars < 0 ) { + // just rewind to the beginning i guess + seek( 0 ); + return; + } + if( tell() == 0 ) { + return; + } + int lines_found = 0; + stream->seekg( -1, std::istream::cur ); + for( int i = 0; i < max_chars; ++i ) { + size_t tellpos = tell(); + if( peek() == '\n' ) { + ++lines_found; + if( tellpos > 0 ) { + stream->seekg( -1, std::istream::cur ); + // note: does not update tellpos or count a character + if( peek() != '\r' ) { + continue; + } + } + } else if( peek() == '\r' ) { + ++lines_found; + } + if( tellpos == 0 ) { + break; + } else if( lines_found == max_lines ) { + // don't include the last \n or \r + stream->seekg( 1, std::istream::cur ); + break; + } + stream->seekg( -1, std::istream::cur ); + } +} + +std::string JsonIn::substr( size_t pos, size_t len ) +{ + std::string ret; + if( len == std::string::npos ) { + stream->seekg( 0, std::istream::end ); + size_t end = tell(); + len = end - pos; + } + ret.resize( len ); + stream->seekg( pos ); + stream->read( &ret[0], len ); + return ret; +} + +JsonOut::JsonOut( std::ostream &s, bool pretty, int depth ) : + stream( &s ), pretty_print( pretty ), indent_level( depth ) +{ + // ensure consistent and locale-independent formatting of numerals + stream->imbue( std::locale::classic() ); + stream->setf( std::ios_base::showpoint ); + stream->setf( std::ios_base::dec, std::ostream::basefield ); + stream->setf( std::ios_base::fixed, std::ostream::floatfield ); + + // automatically stringify bool to "true" or "false" + stream->setf( std::ios_base::boolalpha ); +} + +int JsonOut::tell() +{ + return stream->tellp(); +} + +void JsonOut::seek( int pos ) +{ + stream->clear(); + stream->seekp( pos ); + need_separator = false; +} + +void JsonOut::write_indent() +{ + std::fill_n( std::ostream_iterator( *stream ), indent_level * 2, ' ' ); +} + +void JsonOut::write_separator() +{ + if( !need_separator ) { + return; + } + stream->put( ',' ); + if( pretty_print ) { + // Wrap after seperator between objects and between members of top-level objects. + if( indent_level < 2 || need_wrap.back() ) { + stream->put( '\n' ); + write_indent(); + } else { + // Otherwise pad after commas. + stream->put( ' ' ); + } + } + need_separator = false; +} + +void JsonOut::write_member_separator() +{ + if( pretty_print ) { + stream->write( ": ", 2 ); + } else { + stream->put( ':' ); + } + need_separator = false; +} + +void JsonOut::start_pretty() +{ + if( pretty_print ) { + indent_level += 1; + // Wrap after top level object and array opening. + if( indent_level < 2 || need_wrap.back() ) { + stream->put( '\n' ); + write_indent(); + } else { + // Otherwise pad after opening. + stream->put( ' ' ); + } + } +} + +void JsonOut::end_pretty() +{ + if( pretty_print ) { + indent_level -= 1; + // Wrap after ending top level array and object. + // Also wrap in the special case of exiting an array containing an object. + if( indent_level < 1 || need_wrap.back() ) { + stream->put( '\n' ); + write_indent(); + } else { + // Otherwise pad after ending. + stream->put( ' ' ); + } + } +} + +void JsonOut::start_object( bool wrap ) +{ + if( need_separator ) { + write_separator(); + } + stream->put( '{' ); + need_wrap.push_back( wrap ); + start_pretty(); + need_separator = false; +} + +void JsonOut::end_object() +{ + end_pretty(); + need_wrap.pop_back(); + stream->put( '}' ); + need_separator = true; +} + +void JsonOut::start_array( bool wrap ) +{ + if( need_separator ) { + write_separator(); + } + stream->put( '[' ); + need_wrap.push_back( wrap ); + start_pretty(); + need_separator = false; +} + +void JsonOut::end_array() +{ + end_pretty(); + need_wrap.pop_back(); + stream->put( ']' ); + need_separator = true; +} + +void JsonOut::write_null() +{ + if( need_separator ) { + write_separator(); + } + stream->write( "null", 4 ); + need_separator = true; +} + +void JsonOut::write( const std::string &val ) +{ + if( need_separator ) { + write_separator(); + } + stream->put( '"' ); + for( const auto &i : val ) { + unsigned char ch = i; + if( ch == '"' ) { + stream->write( "\\\"", 2 ); + } else if( ch == '\\' ) { + stream->write( "\\\\", 2 ); + } else if( ch == '/' ) { + // don't technically need to escape this + stream->put( '/' ); + } else if( ch == '\b' ) { + stream->write( "\\b", 2 ); + } else if( ch == '\f' ) { + stream->write( "\\f", 2 ); + } else if( ch == '\n' ) { + stream->write( "\\n", 2 ); + } else if( ch == '\r' ) { + stream->write( "\\r", 2 ); + } else if( ch == '\t' ) { + stream->write( "\\t", 2 ); + } else if( ch < 0x20 ) { + // convert to "\uxxxx" unicode escape + stream->write( "\\u00", 4 ); + stream->put( ( ch < 0x10 ) ? '0' : '1' ); + char remainder = ch & 0x0F; + if( remainder < 0x0A ) { + stream->put( '0' + remainder ); + } else { + stream->put( 'A' + ( remainder - 0x0A ) ); + } + } else { + stream->put( ch ); + } + } + stream->put( '"' ); + need_separator = true; +} + +template +void JsonOut::write( const std::bitset &b ) +{ + if( need_separator ) { + write_separator(); + } + std::string converted = b.to_string(); + stream->put( '"' ); + for( auto &i : converted ) { + unsigned char ch = i; + stream->put( ch ); + } + stream->put( '"' ); + need_separator = true; +} + +void JsonOut::write( const JsonSerializer &thing ) +{ + if( need_separator ) { + write_separator(); + } + thing.serialize( *this ); + need_separator = true; +} + +void JsonOut::member( const std::string &name ) +{ + write( name ); + write_member_separator(); +} + +void JsonOut::null_member( const std::string &name ) +{ + member( name ); + write_null(); +} + +JsonError::JsonError( const std::string &msg ) + : std::runtime_error( msg ) +{ +} + +std::ostream &operator<<( std::ostream &stream, const JsonError &err ) +{ + return stream << err.what(); +} + +// Need to instantiate those template to make them available for other compilation units. +// Currently only bitsets of size 12 are loaded / stored, if you need other sizes, either explicitly +// instantiate them here, or move the templated read/write functions into the header. +template void JsonOut::write<12>( const std::bitset<12> & ); +template bool JsonIn::read<12>( std::bitset<12> &, bool throw_on_error ); + +JsonIn &JsonValue::seek() const +{ + jsin_.seek( pos_ ); + return jsin_; +} + +JsonValue JsonObject::get_member( const std::string &name ) const +{ + const auto iter = positions.find( name ); + if( !jsin || iter == positions.end() ) { + throw_error( "requested non-existing member \"" + name + "\" in " + str() ); + } + mark_visited( name ); + return JsonValue( *jsin, iter->second ); +} diff --git a/Src/json.h b/Src/json.h new file mode 100644 index 0000000000..1bb515c6f0 --- /dev/null +++ b/Src/json.h @@ -0,0 +1,1420 @@ +#pragma once +#ifndef CATA_SRC_JSON_H +#define CATA_SRC_JSON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "colony.h" +#include "enum_conversions.h" +#include "string_id.h" + +/* Cataclysm-DDA homegrown JSON tools + * copyright CC-BY-SA-3.0 2013 CleverRaven + * + * Consists of six JSON manipulation tools: + * JsonIn - for low-level parsing of an input JSON stream + * JsonOut - for outputting JSON + * JsonObject - convenience-wrapper for reading JSON objects from a JsonIn + * JsonArray - convenience-wrapper for reading JSON arrays from a JsonIn + * JsonSerializer - inheritable interface for custom datatype serialization + * JsonDeserializer - inheritable interface for custom datatype deserialization + * + * Further documentation can be found below. + */ + +class JsonArray; +class JsonDeserializer; +class JsonObject; +class JsonSerializer; +class JsonValue; + +namespace cata +{ +template +class optional; +} // namespace cata + +class JsonError : public std::runtime_error +{ + public: + JsonError( const std::string &msg ); + const char *c_str() const noexcept { + return what(); + } +}; + +template +struct key_from_json_string; + +template<> +struct key_from_json_string { + std::string operator()( const std::string &s ) { + return s; + } +}; + +template +struct key_from_json_string, void> { + string_id operator()( const std::string &s ) { + return string_id( s ); + } +}; + +template +struct key_from_json_string::value>> { + Enum operator()( const std::string &s ) { + return io::string_to_enum( s ); + } +}; + +struct number_sci_notation { + bool negative = false; + // AKA the significand + uint64_t number = 0; + // AKA the order of magnitude + int64_t exp = 0; +}; + +/* JsonIn + * ====== + * + * The JsonIn class provides a wrapper around a std::istream, + * with methods for reading JSON data directly from the stream. + * + * JsonObject and JsonArray provide higher-level wrappers, + * and are a little easier to use in most cases, + * but have the small overhead of indexing the members or elements before use. + * + * Typical JsonIn usage might be something like the following: + * + * JsonIn jsin(myistream); + * // expecting an array of objects + * jsin.start_array(); // throws JsonError if array not found + * while (!jsin.end_array()) { // end_array returns false if not the end + * JsonObject jo = jsin.get_object(); + * ... // load object using JsonObject methods + * } + * + * The array could have been loaded into a JsonArray for convenience. + * Not doing so saves one full pass of the data, + * as the element positions don't have to be read beforehand. + * + * If the JSON structure is not as expected, + * verbose error messages are provided, indicating the problem, + * and the exact line number and byte offset within the istream. + * + * + * Single-Pass Loading + * ------------------- + * + * A JsonIn can also be used for single-pass loading, + * by passing off members as they arrive according to their names. + * + * Typical usage might be: + * + * JsonIn jsin(&myistream); + * // expecting an array of objects, to be mapped by id + * std::map myobjects; + * jsin.start_array(); + * while (!jsin.end_array()) { + * my_data_type myobject; + * jsin.start_object(); + * while (!jsin.end_object()) { + * std::string name = jsin.get_member_name(); + * if (name == "id") { + * myobject.id = jsin.get_string(); + * } else if (name == "name") { + * myobject.name = _(jsin.get_string()); + * } else if (name == "description") { + * myobject.description = _(jsin.get_string()); + * } else if (name == "points") { + * myobject.points = jsin.get_int(); + * } else if (name == "flags") { + * if (jsin.test_string()) { // load single string tag + * myobject.tags.insert(jsin.get_string()); + * } else { // load tag array + * jsin.start_array(); + * while (!jsin.end_array()) { + * myobject.tags.insert(jsin.get_string()); + * } + * } + * } else { + * jsin.skip_value(); + * } + * } + * myobjects[myobject.id] = myobject; + * } + * + * The get_* methods automatically verify and skip separators and whitespace. + * + * Using this method, unrecognized members are silently ignored, + * allowing for things like "comment" members, + * but the user must remember to provide an "else" clause, + * explicitly skipping them. + * + * If an if;else if;... is missing the "else", it /will/ cause bugs, + * so preindexing as a JsonObject is safer, as well as tidier. + */ +class JsonIn +{ + private: + std::istream *stream; + bool ate_separator = false; + + void skip_separator(); + void skip_pair_separator(); + void end_value(); + + public: + JsonIn( std::istream &s ) : stream( &s ) {} + JsonIn( const JsonIn & ) = delete; + JsonIn &operator=( const JsonIn & ) = delete; + + bool get_ate_separator() { + return ate_separator; + } + void set_ate_separator( bool s ) { + ate_separator = s; + } + + int tell(); // get current stream position + void seek( int pos ); // seek to specified stream position + char peek(); // what's the next char gonna be? + bool good(); // whether stream is ok + + // advance seek head to the next non-whitespace character + void eat_whitespace(); + // or rewind to the previous one + void uneat_whitespace(); + + // quick skipping for when values don't have to be parsed + void skip_member(); + void skip_string(); + void skip_value(); + void skip_object(); + void skip_array(); + void skip_true(); + void skip_false(); + void skip_null(); + void skip_number(); + + // data parsing + std::string get_string(); // get the next value as a string + int get_int(); // get the next value as an int + unsigned int get_uint(); // get the next value as an unsigned int + int64_t get_int64(); // get the next value as an int64 + uint64_t get_uint64(); // get the next value as a uint64 + bool get_bool(); // get the next value as a bool + double get_float(); // get the next value as a double + std::string get_member_name(); // also strips the ':' + JsonObject get_object(); + JsonArray get_array(); + + template::value>::type> + E get_enum_value() { + const auto old_offset = tell(); + try { + return io::string_to_enum( get_string() ); + } catch( const io::InvalidEnumString & ) { + seek( old_offset ); // so the error message points to the correct place. + error( "invalid enumeration value" ); + } + } + + // container control and iteration + void start_array(); // verify array start + bool end_array(); // returns false if it's not the end + void start_object(); + bool end_object(); // returns false if it's not the end + + // type testing + bool test_null(); + bool test_bool(); + bool test_number(); + bool test_int() { + return test_number(); + } + bool test_float() { + return test_number(); + } + bool test_string(); + bool test_bitset(); + bool test_array(); + bool test_object(); + + // optionally-fatal reading into values by reference + // returns true if the data was read successfully, false otherwise + // if throw_on_error then throws JsonError rather than returning false. + bool read( bool &b, bool throw_on_error = false ); + bool read( char &c, bool throw_on_error = false ); + bool read( signed char &c, bool throw_on_error = false ); + bool read( unsigned char &c, bool throw_on_error = false ); + bool read( short unsigned int &s, bool throw_on_error = false ); + bool read( short int &s, bool throw_on_error = false ); + bool read( int &i, bool throw_on_error = false ); + bool read( int64_t &i, bool throw_on_error = false ); + bool read( uint64_t &i, bool throw_on_error = false ); + bool read( unsigned int &u, bool throw_on_error = false ); + bool read( float &f, bool throw_on_error = false ); + bool read( double &d, bool throw_on_error = false ); + bool read( std::string &s, bool throw_on_error = false ); + template + bool read( std::bitset &b, bool throw_on_error = false ); + bool read( JsonDeserializer &j, bool throw_on_error = false ); + // This is for the string_id type + template + auto read( T &thing, bool throw_on_error = false ) -> decltype( thing.str(), true ) { + std::string tmp; + if( !read( tmp, throw_on_error ) ) { + return false; + } + thing = T( tmp ); + return true; + } + + /// Overload that calls a global function `deserialize(T&,JsonIn&)`, if available. + template + auto read( T &v, bool throw_on_error = false ) -> + decltype( deserialize( v, *this ), true ) { + try { + deserialize( v, *this ); + return true; + } catch( const JsonError & ) { + if( throw_on_error ) { + throw; + } + return false; + } + } + + /// Overload that calls a member function `T::deserialize(JsonIn&)`, if available. + template + auto read( T &v, bool throw_on_error = false ) -> decltype( v.deserialize( *this ), true ) { + try { + v.deserialize( *this ); + return true; + } catch( const JsonError & ) { + if( throw_on_error ) { + throw; + } + return false; + } + } + + template::value, int> = 0> + bool read( T &val, bool throw_on_error = false ) { + int i; + if( read( i, false ) ) { + val = static_cast( i ); + return true; + } + std::string s; + if( read( s, throw_on_error ) ) { + val = io::string_to_enum( s ); + return true; + } + return false; + } + + /// Overload for std::pair + template + bool read( std::pair &p, bool throw_on_error = false ) { + if( !test_array() ) { + return error_or_false( throw_on_error, "Expected json array encoding pair" ); + } + try { + start_array(); + bool result = !end_array() && + read( p.first, throw_on_error ) && + !end_array() && + read( p.second, throw_on_error ) && + end_array(); + if( !result && throw_on_error ) { + error( "Array had wrong number of elements for pair" ); + } + return result; + } catch( const JsonError & ) { + if( throw_on_error ) { + throw; + } + return false; + } + } + + // array ~> vector, deque, list + template < typename T, typename std::enable_if < + !std::is_same::value >::type * = nullptr + > + auto read( T &v, bool throw_on_error = false ) -> decltype( v.front(), true ) { + if( !test_array() ) { + return error_or_false( throw_on_error, "Expected json array" ); + } + try { + start_array(); + v.clear(); + while( !end_array() ) { + typename T::value_type element; + if( read( element, throw_on_error ) ) { + v.push_back( std::move( element ) ); + } else { + skip_value(); + } + } + } catch( const JsonError & ) { + if( throw_on_error ) { + throw; + } + return false; + } + + return true; + } + + // array ~> array + template + bool read( std::array &v, bool throw_on_error = false ) { + if( !test_array() ) { + return error_or_false( throw_on_error, "Expected json array" ); + } + try { + start_array(); + for( size_t i = 0; i < N; ++i ) { + if( end_array() ) { + if( throw_on_error ) { + error( "Json array is too short" ); + } + return false; // json array is too small + } + if( !read( v[i], throw_on_error ) ) { + return false; // invalid entry + } + } + bool result = end_array(); + if( !result && throw_on_error ) { + error( "Array had too many elements" ); + } + return result; + } catch( const JsonError & ) { + if( throw_on_error ) { + throw; + } + return false; + } + } + + // object ~> containers with matching key_type and value_type + // set, unordered_set ~> object + template ::value>::type * = nullptr + > + bool read( T &v, bool throw_on_error = false ) { + if( !test_array() ) { + return error_or_false( throw_on_error, "Expected json array" ); + } + try { + start_array(); + v.clear(); + while( !end_array() ) { + typename T::value_type element; + if( read( element, throw_on_error ) ) { + v.insert( std::move( element ) ); + } else { + skip_value(); + } + } + } catch( const JsonError & ) { + if( throw_on_error ) { + throw; + } + return false; + } + + return true; + } + + // special case for colony as it uses `insert()` instead of `push_back()` + // and therefore doesn't fit with vector/deque/list + template + bool read( cata::colony &v, bool throw_on_error = false ) { + if( !test_array() ) { + return error_or_false( throw_on_error, "Expected json array" ); + } + try { + start_array(); + v.clear(); + while( !end_array() ) { + T element; + if( read( element, throw_on_error ) ) { + v.insert( std::move( element ) ); + } else { + skip_value(); + } + } + } catch( const JsonError & ) { + if( throw_on_error ) { + throw; + } + return false; + } + + return true; + } + + // object ~> containers with unmatching key_type and value_type + // map, unordered_map ~> object + template < typename T, typename std::enable_if < + !std::is_same::value >::type * = nullptr + > + bool read( T &m, bool throw_on_error = true ) { + if( !test_object() ) { + return error_or_false( throw_on_error, "Expected json object" ); + } + try { + start_object(); + m.clear(); + while( !end_object() ) { + using key_type = typename T::key_type; + key_type key = key_from_json_string()( get_member_name() ); + typename T::mapped_type element; + if( read( element, throw_on_error ) ) { + m[std::move( key )] = std::move( element ); + } else { + skip_value(); + } + } + } catch( const JsonError & ) { + if( throw_on_error ) { + throw; + } + return false; + } + + return true; + } + + // error messages + std::string line_number( int offset_modifier = 0 ); // for occasional use only + [[noreturn]] void error( const std::string &message, int offset = 0 ); // ditto + + // If throw_, then call error( message, offset ), otherwise return + // false + bool error_or_false( bool throw_, const std::string &message, int offset = 0 ); + void rewind( int max_lines = -1, int max_chars = -1 ); + std::string substr( size_t pos, size_t len = std::string::npos ); + private: + // This should be used to get any and all numerical data types. + number_sci_notation get_any_number(); + // Calls get_any_number() then applies operations common to all integer types. + number_sci_notation get_any_int(); +}; + +/* JsonOut + * ======= + * + * The JsonOut class provides a straightforward interface for outputting JSON. + * + * It wraps a std::ostream, providing methods for writing JSON data directly. + * + * Typical usage might be as follows: + * + * JsonOut jsout(myostream); + * jsout.start_object(); // { + * jsout.member("type", "point"); // "type":"point" + * jsout.member("data"); // ,"data": + * jsout.start_array(); // [ + * jsout.write(5) // 5 + * jsout.write(9) // ,9 + * jsout.end_array(); // ] + * jsout.end_object(); // } + * + * which writes {"type":"point","data":[5,9]} to myostream. + * + * Separators are handled automatically, + * and the constructor also has an option for crude pretty-printing, + * which inserts newlines and whitespace liberally, if turned on. + * + * Basic containers such as maps, sets and vectors, + * as well as anything inheriting the JsonSerializer interface, + * can be serialized automatically by write() and member(). + */ +class JsonOut +{ + private: + std::ostream *stream; + bool pretty_print; + std::vector need_wrap; + int indent_level = 0; + bool need_separator = false; + + public: + JsonOut( std::ostream &stream, bool pretty_print = false, int depth = 0 ); + JsonOut( const JsonOut & ) = delete; + JsonOut &operator=( const JsonOut & ) = delete; + + // punctuation + void write_indent(); + void write_separator(); + void write_member_separator(); + bool get_need_separator() { + return need_separator; + } + void set_need_separator() { + need_separator = true; + } + std::ostream *get_stream() { + return stream; + } + int tell(); + void seek( int pos ); + void start_pretty(); + void end_pretty(); + + void start_object( bool wrap = false ); + void end_object(); + void start_array( bool wrap = false ); + void end_array(); + + // write data to the output stream as JSON + void write_null(); + + template ::value, int>::type = 0> + void write( T val ) { + if( need_separator ) { + write_separator(); + } + *stream << val; + need_separator = true; + } + + /// Overload that calls a global function `serialize(const T&,JsonOut&)`, if available. + template + auto write( const T &v ) -> decltype( serialize( v, *this ), void() ) { + serialize( v, *this ); + } + + /// Overload that calls a member function `T::serialize(JsonOut&) const`, if available. + template + auto write( const T &v ) -> decltype( v.serialize( *this ), void() ) { + v.serialize( *this ); + } + + template ::value, int>::type = 0> + void write( T val ) { + write( static_cast::type>( val ) ); + } + + // strings need escaping and quoting + void write( const std::string &val ); + void write( const char *val ) { + write( std::string( val ) ); + } + + // char should always be written as an unquoted numeral + void write( char val ) { + write( static_cast( val ) ); + } + void write( signed char val ) { + write( static_cast( val ) ); + } + void write( unsigned char val ) { + write( static_cast( val ) ); + } + + template + void write( const std::bitset &b ); + + void write( const JsonSerializer &thing ); + // This is for the string_id type + template + auto write( const T &thing ) -> decltype( thing.str(), ( void )0 ) { + write( thing.str() ); + } + + // enum ~> string + template ::value>::type * = nullptr> + void write_as_string( const E value ) { + write( io::enum_to_string( value ) ); + } + + void write_as_string( const std::string &s ) { + write( s ); + } + + template + void write_as_string( const string_id &s ) { + write( s ); + } + + template + void write( const std::pair &p ) { + start_array(); + write( p.first ); + write( p.second ); + end_array(); + } + + template + void write_as_array( const T &container ) { + start_array(); + for( const auto &e : container ) { + write( e ); + } + end_array(); + } + + // containers with front() ~> array + // vector, deque, forward_list, list + template < typename T, typename std::enable_if < + !std::is_same::value >::type * = nullptr + > + auto write( const T &container ) -> decltype( container.front(), ( void )0 ) { + write_as_array( container ); + } + + // containers with matching key_type and value_type ~> array + // set, unordered_set + template ::value>::type * = nullptr + > + void write( const T &container ) { + write_as_array( container ); + } + + // special case for colony, since it doesn't fit in other categories + template + void write( const cata::colony &container ) { + write_as_array( container ); + } + + // containers with unmatching key_type and value_type ~> object + // map, unordered_map ~> object + template < typename T, typename std::enable_if < + !std::is_same::value >::type * = nullptr + > + void write( const T &map ) { + start_object(); + for( const auto &it : map ) { + write_as_string( it.first ); + write_member_separator(); + write( it.second ); + } + end_object(); + } + + // convenience methods for writing named object members + // TODO: enforce value after + void member( const std::string &name ); + void null_member( const std::string &name ); + template void member( const std::string &name, const T &value ) { + member( name ); + write( value ); + } + template void member_as_string( const std::string &name, const T &value ) { + member( name ); + write_as_string( value ); + } +}; + +/* JsonObject + * ========== + * + * The JsonObject class provides easy access to incoming JSON object data. + * + * JsonObject maps member names to the byte offset of the paired value, + * given an underlying JsonIn stream. + * + * It provides data by seeking the stream to the relevant position, + * and calling the correct JsonIn method to read the value from the stream. + * + * + * General Usage + * ------------- + * + * JsonObject jo(jsin); + * std::string id = jo.get_string("id"); + * std::string name = _(jo.get_string("name")); + * std::string description = _(jo.get_string("description")); + * int points = jo.get_int("points", 0); + * std::set tags = jo.get_tags("flags"); + * my_object_type myobject(id, name, description, points, tags); + * + * Here the "id", "name" and "description" members are required. + * JsonObject will throw a JsonError if they are not found, + * identifying the problem and the current position in the input stream. + * + * Note that "name" and "description" are passed to gettext for translating. + * Any members with string information that is displayed directly to the user + * will need similar treatment, and may also need to be explicitly handled + * in lang/extract_json_strings.py to be translatable. + * + * The "points" member here is not required. + * If it is not found in the incoming JSON object, + * it will be initialized with the default value given as the second parameter, + * in this case, 0. + * + * get_tags() always returns an empty set if the member is not found, + * and so does not require a default value to be specified. + * + * + * Member Testing and Automatic Deserialization + * -------------------------------------------- + * + * JsonObjects can test for member type with has_int(name) etc., + * and for member existence with has_member(name). + * + * They can also read directly into compatible data structures, + * including sets, vectors, maps, and any class inheriting JsonDeserializer, + * using read(name, value). + * + * read() returns true on success, false on failure. + * + * JsonObject jo(jsin); + * std::vector messages; + * if (!jo.read("messages", messages)) { + * DebugLog() << "No messages."; + * } + * + * + * Automatic error checking + * ------------------------ + * + * By default, when a JsonObject is destroyed (or when you call finish) it will + * check to see whether every member of the object was referenced in some way + * (even simply checking for the existence of the member is sufficient). + * + * If not all the members were referenced, then an error will be written to the + * log (which in particular will cause the tests to fail). + * + * If you don't want this behavior, then call allow_omitted_members() before + * the JsonObject is destroyed. Calling str() also suppresses it (on the basis + * that you may be intending to re-parse that string later). + */ +class JsonObject +{ + private: + std::map positions; + int start; + int end_; + bool final_separator; +#ifndef CATA_IN_TOOL + mutable std::set visited_members; + mutable bool report_unvisited_members = true; + mutable bool reported_unvisited_members = false; +#endif + void mark_visited( const std::string &name ) const; + void report_unvisited() const; + + JsonIn *jsin; + int verify_position( const std::string &name, + bool throw_exception = true ) const; + + public: + JsonObject( JsonIn &jsin ); + JsonObject() : + start( 0 ), end_( 0 ), final_separator( false ), jsin( nullptr ) {} + JsonObject( const JsonObject & ) = default; + JsonObject( JsonObject && ) = default; + JsonObject &operator=( const JsonObject & ) = default; + JsonObject &operator=( JsonObject && ) = default; + ~JsonObject() { + finish(); + } + + class const_iterator; + + friend const_iterator; + + const_iterator begin() const; + const_iterator end() const; + + void finish(); // moves the stream to the end of the object + size_t size() const; + bool empty() const; + + void allow_omitted_members() const; + bool has_member( const std::string &name ) const; // true iff named member exists + std::string str() const; // copy object json as string + [[noreturn]] void throw_error( const std::string &err ) const; + [[noreturn]] void throw_error( const std::string &err, const std::string &name ) const; + // seek to a value and return a pointer to the JsonIn (member must exist) + JsonIn *get_raw( const std::string &name ) const; + JsonValue get_member( const std::string &name ) const; + + // values by name + // variants with no fallback throw an error if the name is not found. + // variants with a fallback return the fallback value in stead. + bool get_bool( const std::string &name ) const; + bool get_bool( const std::string &name, bool fallback ) const; + int get_int( const std::string &name ) const; + int get_int( const std::string &name, int fallback ) const; + double get_float( const std::string &name ) const; + double get_float( const std::string &name, double fallback ) const; + std::string get_string( const std::string &name ) const; + std::string get_string( const std::string &name, const std::string &fallback ) const; + + template::value>::type> + E get_enum_value( const std::string &name, const E fallback ) const { + if( !has_member( name ) ) { + return fallback; + } + mark_visited( name ); + jsin->seek( verify_position( name ) ); + return jsin->get_enum_value(); + } + template::value>::type> + E get_enum_value( const std::string &name ) const { + mark_visited( name ); + jsin->seek( verify_position( name ) ); + return jsin->get_enum_value(); + } + + // containers by name + // get_array returns empty array if the member is not found + JsonArray get_array( const std::string &name ) const; + std::vector get_int_array( const std::string &name ) const; + std::vector get_string_array( const std::string &name ) const; + // get_object returns empty object if not found + JsonObject get_object( const std::string &name ) const; + + // get_tags returns empty set if none found + template + std::set get_tags( const std::string &name ) const; + + // TODO: some sort of get_map(), maybe + + // type checking + bool has_null( const std::string &name ) const; + bool has_bool( const std::string &name ) const; + bool has_number( const std::string &name ) const; + bool has_int( const std::string &name ) const { + return has_number( name ); + } + bool has_float( const std::string &name ) const { + return has_number( name ); + } + bool has_string( const std::string &name ) const; + bool has_array( const std::string &name ) const; + bool has_object( const std::string &name ) const; + + // non-fatally read values by reference + // return true if the value was set. + // return false if the member is not found. + // throw_on_error dictates the behavior when the member was present + // but the read fails. + template + bool read( const std::string &name, T &t, bool throw_on_error = true ) const { + int pos = verify_position( name, false ); + if( !pos ) { + return false; + } + mark_visited( name ); + jsin->seek( pos ); + return jsin->read( t, throw_on_error ); + } + + // useful debug info + std::string line_number() const; // for occasional use only +}; + +/* JsonArray + * ========= + * + * The JsonArray class provides easy access to incoming JSON array data. + * + * JsonArray stores only the byte offset of each element in the JsonIn stream, + * and iterates or accesses according to these offsets. + * + * It provides data by seeking the stream to the relevant position, + * and calling the correct JsonIn method to read the value from the stream. + * + * Arrays can be iterated over, + * or accessed directly by element index. + * + * Elements can be returned as standard data types, + * or automatically read into a compatible container. + * + * + * Iterative Access + * ---------------- + * + * JsonArray ja = jo.get_array("some_array_member"); + * std::vector myarray; + * while (ja.has_more()) { + * myarray.push_back(ja.next_int()); + * } + * + * If the array member is not found, get_array() returns an empty array, + * and has_more() is always false, so myarray will remain empty. + * + * next_int() and the other next_* methods advance the array iterator, + * so after the final element has been read, + * has_more() will return false and the loop will terminate. + * + * If the next element is not an integer, + * JsonArray will throw a JsonError indicating the problem, + * and the position in the input stream. + * + * To handle arrays with elements of indeterminate type, + * the test_* methods can be used, calling next_* according to the result. + * + * Note that this style of iterative access requires an element to be read, + * or else the index will not be incremented, resulting in an infinite loop. + * Unwanted elements can be skipped with JsonArray::skip_value(). + * + * + * Positional Access + * ----------------- + * + * JsonArray ja = jo.get_array("xydata"); + * point xydata(ja.get_int(0), ja.get_int(1)); + * + * Arrays also provide has_int(index) etc., for positional type testing, + * and read(index, &container) for automatic deserialization. + * + * + * Automatic Deserialization + * ------------------------- + * + * Elements can also be automatically read into compatible containers, + * such as maps, sets, vectors, and classes implementing JsonDeserializer, + * using the read_next() and read() methods. + * + * JsonArray ja = jo.get_array("custom_datatype_array"); + * while (ja.has_more()) { + * MyDataType mydata; // MyDataType implementing JsonDeserializer + * ja.read_next(mydata); + * process(mydata); + * } + */ +class JsonArray +{ + private: + std::vector positions; + int start; + size_t index; + int end_; + bool final_separator; + JsonIn *jsin; + void verify_index( size_t i ) const; + + public: + JsonArray( JsonIn &jsin ); + JsonArray( const JsonArray &ja ); + JsonArray() : start( 0 ), index( 0 ), end_( 0 ), final_separator( false ), jsin( nullptr ) {} + ~JsonArray() { + finish(); + } + JsonArray &operator=( const JsonArray & ); + + void finish(); // move the stream position to the end of the array + + bool has_more() const; // true iff more elements may be retrieved with next_* + size_t size() const; + bool empty(); + std::string str(); // copy array json as string + [[noreturn]] void throw_error( const std::string &err ); + [[noreturn]] void throw_error( const std::string &err, int idx ); + + // iterative access + bool next_bool(); + int next_int(); + double next_float(); + std::string next_string(); + JsonArray next_array(); + JsonObject next_object(); + void skip_value(); // ignore whatever is next + + // static access + bool get_bool( size_t index ) const; + int get_int( size_t index ) const; + double get_float( size_t index ) const; + std::string get_string( size_t index ) const; + JsonArray get_array( size_t index ) const; + JsonObject get_object( size_t index ) const; + + // get_tags returns empty set if none found + template + std::set get_tags( size_t index ) const; + + class const_iterator; + + const_iterator begin() const; + const_iterator end() const; + + // iterative type checking + bool test_null() const; + bool test_bool() const; + bool test_number() const; + bool test_int() const { + return test_number(); + } + bool test_float() const { + return test_number(); + } + bool test_string() const; + bool test_bitset() const; + bool test_array() const; + bool test_object() const; + + // random-access type checking + bool has_null( size_t index ) const; + bool has_bool( size_t index ) const; + bool has_number( size_t index ) const; + bool has_int( size_t index ) const { + return has_number( index ); + } + bool has_float( size_t index ) const { + return has_number( index ); + } + bool has_string( size_t index ) const; + bool has_array( size_t index ) const; + bool has_object( size_t index ) const; + + // iteratively read values by reference + template bool read_next( T &t ) { + verify_index( index ); + jsin->seek( positions[index++] ); + return jsin->read( t ); + } + // random-access read values by reference + template bool read( size_t i, T &t, bool throw_on_error = false ) const { + verify_index( i ); + jsin->seek( positions[i] ); + return jsin->read( t, throw_on_error ); + } +}; + +class JsonValue +{ + private: + JsonIn &jsin_; + int pos_; + + JsonIn &seek() const; + + public: + JsonValue( JsonIn &jsin, int pos ) : jsin_( jsin ), pos_( pos ) { } + + operator std::string() const { + return seek().get_string(); + } + operator int() const { + return seek().get_int(); + } + operator bool() const { + return seek().get_bool(); + } + operator double() const { + return seek().get_float(); + } + operator JsonObject() const { + return seek().get_object(); + } + operator JsonArray() const { + return seek().get_array(); + } + template + bool read( T &t, bool throw_on_error = false ) const { + return seek().read( t, throw_on_error ); + } + + bool test_string() const { + return seek().test_string(); + } + bool test_int() const { + return seek().test_int(); + } + bool test_bool() const { + return seek().test_bool(); + } + bool test_float() const { + return seek().test_float(); + } + bool test_object() const { + return seek().test_object(); + } + bool test_array() const { + return seek().test_array(); + } + + [[noreturn]] void throw_error( const std::string &err ) const { + seek().error( err ); + } + + std::string get_string() const { + return seek().get_string(); + } + int get_int() const { + return seek().get_int(); + } + bool get_bool() const { + return seek().get_bool(); + } + double get_float() const { + return seek().get_float(); + } + JsonObject get_object() const { + return seek().get_object(); + } + JsonArray get_array() const { + return seek().get_array(); + } +}; + +class JsonArray::const_iterator +{ + private: + JsonArray array_; + size_t index_; + + public: + const_iterator( const JsonArray &array, size_t index ) : array_( array ), index_( index ) { } + + const_iterator &operator++() { + index_++; + return *this; + } + JsonValue operator*() const { + array_.verify_index( index_ ); + return JsonValue( *array_.jsin, array_.positions[index_] ); + } + + friend bool operator==( const const_iterator &lhs, const const_iterator &rhs ) { + return lhs.index_ == rhs.index_; + } + friend bool operator!=( const const_iterator &lhs, const const_iterator &rhs ) { + return !operator==( lhs, rhs ); + } +}; + +inline JsonArray::const_iterator JsonArray::begin() const +{ + return const_iterator( *this, 0 ); +} + +inline JsonArray::const_iterator JsonArray::end() const +{ + return const_iterator( *this, size() ); +} +/** + * Represents a member of a @ref JsonObject. This is retured when one iterates over + * a JsonObject. + * It *is* @ref JsonValue, which is the value of the member, which allows one to write: + +for( const JsonMember &member : some_json_object ) + JsonArray array = member.get_array(); +} + + */ +class JsonMember : public JsonValue +{ + private: + const std::string &name_; + + public: + JsonMember( const std::string &name, const JsonValue &value ) : JsonValue( value ), + name_( name ) { } + + const std::string &name() const { + return name_; + } + /** + * @returns Whether this member is considered a comment. + * Comments should generally be ignored by game, but they should be kept + * when this class is used within a generic JSON tool. + */ + bool is_comment() const { + return name_ == "//"; + } +}; + +class JsonObject::const_iterator +{ + private: + const JsonObject &object_; + decltype( JsonObject::positions )::const_iterator iter_; + + public: + const_iterator( const JsonObject &object, const decltype( iter_ ) &iter ) : object_( object ), + iter_( iter ) { } + + const_iterator &operator++() { + iter_++; + return *this; + } + JsonMember operator*() const { + object_.mark_visited( iter_->first ); + return JsonMember( iter_->first, JsonValue( *object_.jsin, iter_->second ) ); + } + + friend bool operator==( const const_iterator &lhs, const const_iterator &rhs ) { + return lhs.iter_ == rhs.iter_; + } + friend bool operator!=( const const_iterator &lhs, const const_iterator &rhs ) { + return !operator==( lhs, rhs ); + } +}; + +inline JsonObject::const_iterator JsonObject::begin() const +{ + return const_iterator( *this, positions.begin() ); +} + +inline JsonObject::const_iterator JsonObject::end() const +{ + return const_iterator( *this, positions.end() ); +} + +template +std::set JsonArray::get_tags( const size_t index ) const +{ + std::set res; + + verify_index( index ); + jsin->seek( positions[ index ] ); + + // allow single string as tag + if( jsin->test_string() ) { + res.insert( T( jsin->get_string() ) ); + return res; + } + + for( const std::string line : jsin->get_array() ) { + res.insert( T( line ) ); + } + + return res; +} + +template +std::set JsonObject::get_tags( const std::string &name ) const +{ + std::set res; + int pos = verify_position( name, false ); + if( !pos ) { + return res; + } + mark_visited( name ); + jsin->seek( pos ); + + // allow single string as tag + if( jsin->test_string() ) { + res.insert( T( jsin->get_string() ) ); + return res; + } + + // otherwise assume it's an array and error if it isn't. + for( const std::string line : jsin->get_array() ) { + res.insert( T( line ) ); + } + + return res; +} + +/** + * Get an array member from json with name name. For each element of that + * array (which should be a string) add it to the given set. + */ +void add_array_to_set( std::set &, const JsonObject &json, const std::string &name ); + +/* JsonSerializer + * ============== + * + * JsonSerializer is an inheritable interface, + * allowing classes to define how they are to be serialized, + * and then treated as a basic type for serialization purposes. + * + * All a class must to do satisfy this interface, + * is define a `void serialize(JsonOut&) const` method, + * which should use the provided JsonOut to write its data as JSON. + * + * class point : public JsonSerializer { + * int x, y; + * void serialize(JsonOut &jsout) const { + * jsout.start_array(); + * jsout.write(x); + * jsout.write(y); + * jsout.end_array(); + * } + * } + */ +class JsonSerializer +{ + public: + virtual ~JsonSerializer() = default; + virtual void serialize( JsonOut &jsout ) const = 0; + JsonSerializer() = default; + JsonSerializer( JsonSerializer && ) = default; + JsonSerializer( const JsonSerializer & ) = default; + JsonSerializer &operator=( JsonSerializer && ) = default; + JsonSerializer &operator=( const JsonSerializer & ) = default; +}; + +/* JsonDeserializer + * ============== + * + * JsonDeserializer is an inheritable interface, + * allowing classes to define how they are to be deserialized, + * and then treated as a basic type for deserialization purposes. + * + * All a class must to do satisfy this interface, + * is define a `void deserialize(JsonIn&)` method, + * which should read its data from the provided JsonIn, + * assuming it to be in the correct form. + * + * class point : public JsonDeserializer { + * int x, y; + * void deserialize(JsonIn &jsin) { + * JsonArray ja = jsin.get_array(); + * x = ja.get_int(0); + * y = ja.get_int(1); + * } + * } + */ +class JsonDeserializer +{ + public: + virtual ~JsonDeserializer() = default; + virtual void deserialize( JsonIn &jsin ) = 0; + JsonDeserializer() = default; + JsonDeserializer( JsonDeserializer && ) = default; + JsonDeserializer( const JsonDeserializer & ) = default; + JsonDeserializer &operator=( JsonDeserializer && ) = default; + JsonDeserializer &operator=( const JsonDeserializer & ) = default; +}; + +std::ostream &operator<<( std::ostream &stream, const JsonError &err ); + +template +void serialize( const cata::optional &obj, JsonOut &jsout ) +{ + if( obj ) { + jsout.write( *obj ); + } else { + jsout.write_null(); + } +} + +template +void deserialize( cata::optional &obj, JsonIn &jsin ) +{ + if( jsin.test_null() ) { + obj.reset(); + } else { + obj.emplace(); + jsin.read( *obj, true ); + } +} + +#endif // CATA_SRC_JSON_H diff --git a/Src/optional.h b/Src/optional.h new file mode 100644 index 0000000000..f4bb77209c --- /dev/null +++ b/Src/optional.h @@ -0,0 +1,257 @@ +#pragma once +#ifndef CATA_SRC_OPTIONAL_H +#define CATA_SRC_OPTIONAL_H + +#include +#include +#include +#include + +namespace cata +{ + +class bad_optional_access : public std::logic_error +{ + public: + bad_optional_access() : logic_error( "cata::optional: no value contained" ) { } +}; + +struct nullopt_t { + explicit constexpr nullopt_t( int ) {} +}; +static constexpr nullopt_t nullopt{ 0 }; + +struct in_place_t { + explicit in_place_t() = default; +}; +static constexpr in_place_t in_place{ }; + +template +class optional +{ + private: + using StoredType = typename std::remove_const::type; + union { + char dummy; + StoredType data; + }; + bool full; + + T &get() { + assert( full ); + return data; + } + const T &get() const { + assert( full ); + return data; + } + + template + void construct( Args &&... args ) { + assert( !full ); + new( &data )StoredType( std::forward( args )... ); + full = true; + } + void destruct() { + data.~StoredType(); + } + + public: + constexpr optional() noexcept : dummy(), full( false ) { } + constexpr optional( const nullopt_t ) noexcept : dummy(), full( false ) { } + + optional( const optional &other ) : full( false ) { + if( other.full ) { + construct( other.get() ); + } + } + optional( optional &&other ) noexcept : full( false ) { + if( other.full ) { + construct( std::move( other.get() ) ); + } + } + template + explicit optional( in_place_t, Args &&... args ) : data( std::forward( args )... ), + full( true ) { } + + template + explicit optional( in_place_t, std::initializer_list ilist, + Args &&... args ) : data( ilist, + std::forward( args )... ), full( true ) { } + + template < typename U = T, + typename std::enable_if < + !std::is_same, typename std::decay::type>::value && + std::is_constructible < T, U && >::value && + std::is_convertible < U &&, T >::value, bool >::type = true > + // NOLINTNEXTLINE(bugprone-forwarding-reference-overload) + optional( U && t ) + : optional( in_place, std::forward( t ) ) { } + + template < typename U = T, + typename std::enable_if < + !std::is_same, std::decay>::value && + std::is_constructible < T, U && >::value && + !std::is_convertible < U &&, T >::value, bool >::type = false > + // NOLINTNEXTLINE(bugprone-forwarding-reference-overload) + explicit optional( U && t ) + : optional( in_place, std::forward( t ) ) { } + + ~optional() { + reset(); + } + + constexpr const T *operator->() const { + return &get(); + } + T *operator->() { + return &get(); + } + constexpr const T &operator*() const { + return get(); + } + T &operator*() { + return get(); + } + + constexpr explicit operator bool() const noexcept { + return full; + } + constexpr bool has_value() const noexcept { + return full; + } + + T &value() { + if( !full ) { + throw bad_optional_access(); + } + return get(); + } + const T &value() const { + if( !full ) { + throw bad_optional_access(); + } + return get(); + } + + template + T value_or( O &&other ) const { + return full ? get() : static_cast( other ); + } + + template + T &emplace( Args &&... args ) { + reset(); + construct( std::forward( args )... ); + return get(); + } + template + T &emplace( std::initializer_list ilist, Args &&... args ) { + reset(); + construct( ilist, std::forward( args )... ); + return get(); + } + + void reset() noexcept { + if( full ) { + full = false; + destruct(); + } + } + + optional &operator=( nullopt_t ) noexcept { + reset(); + return *this; + } + optional &operator=( const optional &other ) { + if( full && other.full ) { + get() = other.get(); + } else if( full ) { + reset(); + } else if( other.full ) { + construct( other.get() ); + } + return *this; + } + optional &operator=( optional &&other ) noexcept { + if( full && other.full ) { + get() = std::move( other.get() ); + } else if( full ) { + reset(); + } else if( other.full ) { + construct( std::move( other.get() ) ); + } + return *this; + } + template < class U = T, + typename std::enable_if < + !std::is_same, typename std::decay::type>::value && + std::is_constructible < T, U && >::value && + std::is_convertible < U &&, T >::value, bool >::type = true > + optional & operator=( U && value ) { + if( full ) { + get() = std::forward( value ); + } else { + construct( std::forward( value ) ); + } + return *this; + } + template + optional &operator=( const optional &other ) { + if( full && other.full ) { + get() = other.get(); + } else if( full ) { + reset(); + } else if( other.full ) { + construct( other.get() ); + } + return *this; + } + template + optional &operator=( optional &&other ) { + if( full && other.full ) { + get() = std::move( other.get() ); + } else if( full ) { + reset(); + } else if( other.full ) { + construct( std::move( other.get() ) ); + } + return *this; + } + + void swap( optional &other ) { + using std::swap; + + if( full && other.full ) { + swap( get(), other.get() ); + } else if( other.full() ) { + construct( std::move( other.get() ) ); + other.destruct(); + } else if( full ) { + other.construct( std::move( get() ) ); + destruct(); + } + } +}; + +template +constexpr bool operator==( const optional &lhs, const optional &rhs ) +{ + if( lhs.has_value() != rhs.has_value() ) { + return false; + } else if( !lhs ) { + return true; + } else { + return *lhs == *rhs; + } +} + +template< class T, class U > +constexpr bool operator!=( const optional &lhs, const optional &rhs ) +{ + return !operator==( lhs, rhs ); +} + +} // namespace cata + +#endif // CATA_SRC_OPTIONAL_H diff --git a/Src/string_formatter.h b/Src/string_formatter.h new file mode 100644 index 0000000000..5c9220470d --- /dev/null +++ b/Src/string_formatter.h @@ -0,0 +1,428 @@ +#pragma once +#ifndef CATA_SRC_STRING_FORMATTER_H +#define CATA_SRC_STRING_FORMATTER_H + +#include +#include +#include +#include +#include + +// needed for the workaround for the std::to_string bug in some compilers +#include "compatibility.h" // IWYU pragma: keep +// TODO: replace with std::optional +#include "optional.h" + +class translation; + +namespace cata +{ + +class string_formatter; + +// wrapper to allow calling string_formatter::throw_error before the definition of string_formatter +[[noreturn]] +void throw_error( const string_formatter &, const std::string & ); +// wrapper to access string_formatter::temp_buffer before the definition of string_formatter +const char *string_formatter_set_temp_buffer( const string_formatter &, const std::string & ); +// Handle currently active exception from string_formatter and return it as string +std::string handle_string_format_error(); + +/** + * @defgroup string_formatter_convert Convert functions for @ref string_formatter + * + * The `convert` functions here are used to convert the input value of + * @ref string_formatter::parse into the requested type, as defined by the format specifiers. + * + * @tparam T the input type, as given by the call to `string_format`. + * @tparam RT the requested type. The `convert` functions return such a value or they throw + * an exception via @ref throw_error. + * + * Each function has the same parameters: + * First parameter defined the requested type. The value of the pointer is ignored, callers + * should use a (properly casted) `nullptr`. It is required to "simulate" overloading the + * return value. E.g. `long convert(long*, int)` and `short convert(short*, int)` both convert + * a input value of type `int`, but the first converts to `long` and the second converts to + * `short`. Without the first parameters their signature would be identical. + * The second parameter is used to call @ref throw_error / @ref string_formatter_set_temp_buffer. + * The third parameter is the input value that is to be converted. + * The fourth parameter is a dummy value, it is always ignored, callers should use `0` here. + * It is used so the fallback with the variadic arguments is *only* chosen when no other + * overload matches. + */ +/**@{*/ +// Test for arithmetic type, *excluding* bool. printf can not handle bool, so can't we. +template +using is_numeric = typename std::conditional < + std::is_arithmetic::type>::value && + !std::is_same::type, bool>::value, std::true_type, std::false_type >::type; +// Test for integer type (not floating point, not bool). +template +using is_integer = typename std::conditional < is_numeric::value && + !std::is_floating_point::type>::value, std::true_type, + std::false_type >::type; +template +using is_char = typename + std::conditional::type, char>::value, std::true_type, std::false_type>::type; +// Test for std::string type. +template +using is_string = typename + std::conditional::type, std::string>::value, std::true_type, std::false_type>::type; +// Test for c-string type. +template +using is_cstring = typename std::conditional < + std::is_same::type, const char *>::value || + std::is_same::type, char *>::value, std::true_type, std::false_type >::type; +// Test for class translation +template +using is_translation = typename std::conditional < + std::is_same::type, translation>::value, std::true_type, + std::false_type >::type; + +template +inline typename std::enable_if < is_integer::value &&is_integer::value, + RT >::type convert( RT *, const string_formatter &, T &&value, int ) +{ + return value; +} +template +inline typename std::enable_if < is_integer::value +&&std::is_enum::type>::value, +RT >::type convert( RT *, const string_formatter &, T &&value, int ) +{ + return static_cast( value ); +} +template +inline typename std::enable_if < std::is_floating_point::value &&is_numeric::value +&&!is_integer::value, RT >::type convert( RT *, const string_formatter &, T &&value, int ) +{ + return value; +} +template +inline typename std::enable_if < std::is_same::value +&&std::is_pointer::type>::value, void * >::type convert( RT *, + const string_formatter &, T &&value, int ) +{ + return const_cast::type>::type>::type *> + ( value ); +} +template +inline typename std::enable_if < std::is_same::value &&is_string::value, + const char * >::type convert( RT *, const string_formatter &, T &&value, int ) +{ + return value.c_str(); +} +template +inline typename std::enable_if < std::is_same::value &&is_cstring::value, + const char * >::type convert( RT *, const string_formatter &, T &&value, int ) +{ + return value; +} +template +inline typename std::enable_if < std::is_same::value &&is_translation::value, + const char * >::type convert( RT *, const string_formatter &sf, T &&value, int ) +{ + return string_formatter_set_temp_buffer( sf, value.translated() ); +} +template +inline typename std::enable_if < std::is_same::value &&is_numeric::value +&&!is_char::value, const char * >::type convert( RT *, const string_formatter &sf, T &&value, + int ) +{ + return string_formatter_set_temp_buffer( sf, to_string( value ) ); +} +template +inline typename std::enable_if < std::is_same::value &&is_numeric::value +&&is_char::value, const char * >::type convert( RT *, const string_formatter &sf, T &&value, + int ) +{ + return string_formatter_set_temp_buffer( sf, std::string( 1, value ) ); +} +// Catch all remaining conversions (the '...' makes this the lowest overload priority). +// The static_assert is used to restrict the input type to those that can actually be printed, +// calling `string_format` with an unknown type will trigger a compile error because no other +// `convert` function will match, while this one will give a static_assert error. +template +// NOLINTNEXTLINE(cert-dcl50-cpp) +inline RT convert( RT *, const string_formatter &sf, T &&, ... ) +{ + static_assert( std::is_pointer::type>::value || + is_numeric::value || is_string::value || is_char::value || + std::is_enum::type>::value || + is_cstring::value || is_translation::value, "Unsupported argument type" ); + throw_error( sf, "Tried to convert argument of type " + + std::string( typeid( T ).name() ) + " to " + + std::string( typeid( RT ).name() ) + ", which is not possible" ); +} +/**@}*/ + +/** + * Type-safe and undefined-behavior free wrapper over `sprintf`. + * See @ref string_format for usage. + * Basically it extracts the format specifiers and calls `sprintf` for each one separately + * and with proper conversion of the input type. + * For example `printf("%f", 7)` would yield undefined behavior as "%f" requires a `double` + * as argument. This class detects the format specifier and converts the input to `double` + * before calling `sprintf`. Similar for `printf("%d", "foo")` (yields UB again), but this + * class will just throw an exception. + */ +// Note: argument index is always 0-based *in this code*, but `printf` has 1-based arguments. +class string_formatter +{ + private: + /// Complete format string, including all format specifiers (the string passed + /// to @ref printf). + const std::string format; + /// Used during parsing to denote the *next* character in @ref format to be + /// parsed. + size_t current_index_in_format = 0; + /// The formatted output string, filled during parsing of @ref format, + /// so it's only valid after the parsing has completed. + std::string output; + /// The *currently parsed format specifiers. This is extracted from @ref format + /// during parsing and given to @ref sprintf (along with the actual argument). + /// It is filled and reset during parsing for each format specifier in @ref format. + std::string current_format; + /// The *index* (not number) of the next argument to be formatted via @ref current_format. + int current_argument_index = 0; + /// Return the next character from @ref format and increment @ref current_index_in_format. + /// Returns a null-character when the end of the @ref format has been reached (and does not + /// change @ref current_index_in_format). + char consume_next_input(); + /// Returns (like @ref consume_next_input) the next character from @ref format, but + /// does *not* change @ref current_index_in_format. + char get_current_input() const; + /// If the next character to read from @ref format is the given character, consume it + /// (like @ref consume_next_input) and return `true`. Otherwise don't do anything at all + /// and return `false`. + bool consume_next_input_if( char c ); + /// Return whether @ref get_current_input has a decimal digit ('0'...'9'). + bool has_digit() const; + /// Consume decimal digits, interpret them as integer and return it. + /// A starting '0' is allowed. Leaves @ref format at the first non-digit + /// character (or the end). Returns 0 if the first character is not a digit. + int parse_integer(); + /// Read and consume format flag characters and append them to @ref current_format. + /// Leaves @ref format at the first character that is not a flag (or the end). + void read_flags(); + /// Read and forward to @ref current_format any width specifier from @ref format. + /// Returns nothing if the width is not specified or if it is specified as fixed number, + /// otherwise returns the index of the printf-argument to be used for the width. + cata::optional read_width(); + /// See @ref read_width. This does the same, but for the precision specifier. + cata::optional read_precision(); + /// Read and return the index of the printf-argument that is to be formatted. Returns + /// nothing if @ref format does not refer to a specific index (caller should use + /// @ref current_argument_index). + cata::optional read_argument_index(); + // Helper for common logic in @ref read_width and @ref read_precision. + cata::optional read_number_or_argument_index(); + /// Throws an exception containing the given message and the @ref format. + [[noreturn]] + void throw_error( const std::string &msg ) const; + friend void throw_error( const string_formatter &sf, const std::string &msg ) { + sf.throw_error( msg ); + } + mutable std::string temp_buffer; + /// Stores the given text in @ref temp_buffer and returns `c_str()` of it. This is used + /// for printing non-strings through "%s". It *only* works because this prints each format + /// specifier separately, so the content of @ref temp_buffer is only used once. + friend const char *string_formatter_set_temp_buffer( const string_formatter &sf, + const std::string &text ) { + sf.temp_buffer = text; + return sf.temp_buffer.c_str(); + } + /** + * Extracts a printf argument from the argument list and converts it to the requested type. + * @tparam RT The type that the argument should be converted to. + * @tparam current_index The index of the first of the supplied arguments. + * @throws If there is no argument with the given index, or if the argument can not be + * converted to the requested type (via @ref convert). + */ + /**@{*/ + template + RT get_nth_arg_as( const unsigned int requested ) const { + throw_error( "Requested argument " + to_string( requested ) + " but input has only " + to_string( + current_index ) ); + } + template + RT get_nth_arg_as( const unsigned int requested, T &&head, Args &&... args ) const { + if( requested > current_index ) { + return get_nth_arg_as < RT, current_index + 1 > ( requested, std::forward( args )... ); + } else { + return convert( static_cast( nullptr ), *this, std::forward( head ), 0 ); + } + } + /**@}*/ + + void add_long_long_length_modifier(); + + template + void read_conversion( const int format_arg_index, Args &&... args ) { + // Removes the prefix "ll", "l", "h" and "hh", "z", and "t". + // We later add "ll" again and that + // would interfere with the existing prefix. We convert *all* input to (un)signed + // long long int and use the "ll" modifier all the time. This will print the + // expected value all the time, even when the original modifier did not match. + if( consume_next_input_if( 'l' ) ) { + consume_next_input_if( 'l' ); + } else if( consume_next_input_if( 'h' ) ) { + consume_next_input_if( 'h' ); + } else if( consume_next_input_if( 'z' ) ) { + // done with it + } else if( consume_next_input_if( 't' ) ) { + // done with it + } + const char c = consume_next_input(); + current_format.push_back( c ); + switch( c ) { + case 'c': + return do_formating( get_nth_arg_as( format_arg_index, std::forward( args )... ) ); + case 'd': + case 'i': + add_long_long_length_modifier(); + return do_formating( get_nth_arg_as( format_arg_index, + std::forward( args )... ) ); + case 'o': + case 'u': + case 'x': + case 'X': + add_long_long_length_modifier(); + return do_formating( get_nth_arg_as( format_arg_index, + std::forward( args )... ) ); + case 'a': + case 'A': + case 'g': + case 'G': + case 'f': + case 'F': + case 'e': + case 'E': + return do_formating( get_nth_arg_as( format_arg_index, std::forward( args )... ) ); + case 'p': + return do_formating( get_nth_arg_as( format_arg_index, + std::forward( args )... ) ); + case 's': + return do_formating( get_nth_arg_as( format_arg_index, + std::forward( args )... ) ); + default: + throw_error( "Unsupported format conversion: " + std::string( 1, c ) ); + } + } + + template + void do_formating( T &&value ) { + output.append( raw_string_format( current_format.c_str(), value ) ); + } + + public: + /// @param format The format string as required by `sprintf`. + string_formatter( std::string format ) : format( std::move( format ) ) { } + /// Does the actual `sprintf`. It uses @ref format and puts the formatted + /// string into @ref output. + /// Note: use @ref get_output to get the formatted string after a successful + /// call to this function. + /// @throws Exceptions when the arguments do not match the format specifiers, + /// see @ref get_nth_arg_as, or when the format is invalid for whatever reason. + /// Note: @ref string_format is a wrapper that handles those exceptions. + template + void parse( Args &&... args ) { + output.reserve( format.size() ); + output.resize( 0 ); + current_index_in_format = 0; + current_argument_index = 0; + while( const char c = consume_next_input() ) { + if( c != '%' ) { + output.push_back( c ); + continue; + } + if( consume_next_input_if( '%' ) ) { + output.push_back( '%' ); + continue; + } + current_format = "%"; + const cata::optional format_arg_index = read_argument_index(); + read_flags(); + if( const cata::optional width_argument_index = read_width() ) { + const int w = get_nth_arg_as( *width_argument_index, std::forward( args )... ); + current_format += to_string( w ); + } + if( const cata::optional precision_argument_index = read_precision() ) { + const int p = get_nth_arg_as( *precision_argument_index, std::forward( args )... ); + current_format += to_string( p ); + } + const int arg = format_arg_index ? *format_arg_index : current_argument_index++; + read_conversion( arg, std::forward( args )... ); + } + } + std::string get_output() const { + return output; + } +#if defined(__clang__) +#define PRINTF_LIKE(a,b) __attribute__((format(printf,a,b))) +#elif defined(__GNUC__) +#define PRINTF_LIKE(a,b) __attribute__((format(gnu_printf,a,b))) +#else +#define PRINTF_LIKE(a,b) +#endif + /** + * Wrapper for calling @ref vsprintf - see there for documentation. Try to avoid it as it's + * not type safe and may easily lead to undefined behavior - use @ref string_format instead. + * @throws std::exception if the format is invalid / does not match the arguments, but that's + * not guaranteed - technically it's undefined behavior. + */ + // Implemented in output.cpp + static std::string raw_string_format( const char *format, ... ) PRINTF_LIKE( 1, 2 ); +#undef PRINTF_LIKE +}; + +} // namespace cata + +/** + * Simple wrapper over @ref string_formatter::parse. It catches any exceptions and returns + * some error string. Otherwise it just returns the formatted string. + * + * These functions perform string formatting according to the rules of the `printf` function, + * see `man 3 printf` or any other documentation. + * + * In short: the \p format parameter is a string with optional placeholders, which will be + * replaced with formatted data from the further arguments. The further arguments must have + * a type that matches the type expected by the placeholder. + * The placeholders look like this: + * - `%s` expects an argument of type `const char*` or `std::string` or numeric (which is + * converted to a string via `to_string`), which is inserted as is. + * - `%d` expects an argument of an integer type (int, short, ...), which is formatted as + * decimal number. + * - `%f` expects a numeric argument (integer / floating point), which is formatted as + * decimal number. + * + * There are more placeholders and options to them (see documentation of `printf`). + * Note that this wrapper (via @ref string_formatter) automatically converts the arguments + * to match the given format specifier (if possible) - see @ref string_formatter_convert. + */ +/**@{*/ +template +inline std::string string_format( std::string format, Args &&...args ) +{ + try { + cata::string_formatter formatter( std::move( format ) ); + formatter.parse( std::forward( args )... ); + return formatter.get_output(); + } catch( ... ) { + return cata::handle_string_format_error(); + } +} +template +inline std::string string_format( const char *const format, Args &&...args ) +{ + return string_format( std::string( format ), std::forward( args )... ); +} +template +inline typename std::enable_if::value, std::string>::type +string_format( T &&format, Args &&...args ) +{ + return string_format( format.translated(), std::forward( args )... ); +} +/**@}*/ + +#endif // CATA_SRC_STRING_FORMATTER_H diff --git a/Src/string_id.h b/Src/string_id.h new file mode 100644 index 0000000000..f5d2c6b179 --- /dev/null +++ b/Src/string_id.h @@ -0,0 +1,205 @@ +#pragma once +#ifndef CATA_SRC_STRING_ID_H +#define CATA_SRC_STRING_ID_H + +#include +#include + +template +class int_id; + +/** + * This represents an identifier (implemented as std::string) of some object. + * It can be used for all type of objects, one just needs to specify a type as + * template parameter T, which separates the different identifier types. + * + * The constructor is explicit on purpose, you have to write + * \code + * auto someid = mtype_id("mon_dog"); + * if( critter.type->id == mtype_id("mon_cat") ) { ... + * \endcode + * This allows to find all ids that are initialized from static literals (and not + * through the json loading). The can than easily be checked against the json + * definition to see whether they are actually defined there (or whether they are + * defined only in a mod). + * + * Example: + * \code + * struct itype; + * using itype_id = string_id; + * struct mtype; + * using mtype_id = string_id; + * \endcode + * The types mtype_id and itype_id declared here are separate, the compiler will not + * allow assignment / comparison of mtype_id and itype_id. + * Note that a forward declaration is sufficient for the template parameter type. + * + * If an id is used locally in just one header & source file, then feel free to + * define it in those files. If it is used more widely (like mtype_id), then + * please define it in type_id.h, a central light-weight header that defines all ids + * people might want to use. This prevents duplicate definitions in many + * files. + */ +template +class string_id +{ + public: + using This = string_id; + + /** + * Forwarding constructor, forwards any parameter to the std::string + * constructor to create the id string. This allows plain C-strings, + * and std::strings to be used. + */ + // Beautiful C++11: enable_if makes sure that S is always something that can be used to constructor + // a std::string, otherwise a "no matching function to call..." error is generated. + template::value>::type > + explicit string_id( S && id, int cid = -1 ) : _id( std::forward( id ) ), _cid( cid ) { + } + /** + * Default constructor constructs an empty id string. + * Note that this id class does not enforce empty id strings (or any specific string at all) + * to be special. Every string (including the empty one) may be a valid id. + */ + string_id() : _cid( -1 ) {} + /** + * Comparison, only useful when the id is used in std::map or std::set as key. Compares + * the string id as with the strings comparison. + */ + bool operator<( const This &rhs ) const { + return _id < rhs._id; + } + /** + * The usual comparator, compares the string id as usual. + */ + bool operator==( const This &rhs ) const { + return _id == rhs._id; + } + /** + * The usual comparator, compares the string id as usual. + */ + bool operator!=( const This &rhs ) const { + return _id != rhs._id; + } + /** + * Interface to the plain C-string of the id. This function mimics the std::string + * object. Ids are often used in debug messages, where they are forwarded as C-strings + * to be included in the format string, e.g. debugmsg("invalid id: %s", id.c_str()) + */ + const char *c_str() const { + return _id.c_str(); + } + /** + * Returns the identifier as plain std::string. Use with care, the plain string does not + * have any information as what type of object it refers to (the T template parameter of + * the class). + */ + const std::string &str() const { + return _id; + } + + explicit operator std::string() const { + return _id; + } + + // Those are optional, you need to implement them on your own if you want to use them. + // If you don't implement them, but use them, you'll get a linker error. + /** + * Translate the string based it to the matching integer based id. + * This may issue a debug message if the string is not a valid id. + */ + int_id id() const; + /** + * Returns the actual object this id refers to. May show a debug message if the id is invalid. + */ + const T &obj() const; + + const T &operator*() const { + return obj(); + + } + const T *operator->() const { + return &obj(); + } + + /** + * Returns whether this id is valid, that means whether it refers to an existing object. + */ + bool is_valid() const; + /** + * Returns whether this id is empty. An empty id can still be valid, + * and emptiness does not mean that it's null. Named is_empty() to + * keep consistency with the rest is_.. functions + */ + bool is_empty() const { + return _id.empty(); + } + /** + * Returns a null id whose `string_id::is_null()` must always return true. See @ref is_null. + * Specializations are defined in string_id_null_ids.cpp to avoid instantiation ordering issues. + */ + static const string_id &NULL_ID(); + /** + * Returns whether this represents the id of the null-object (in which case it's the null-id). + * Note that not all types assigned to T may have a null-object. As such, there won't be a + * definition of @ref NULL_ID and if you use any of the related functions, you'll get + * errors during the linking. + * + * Example: "mon_null" is the id of the null-object of monster type. + * + * Note: per definition the null-id shall be valid. This allows to use it in places + * that require a (valid) id, but it can still represent a "don't use it" value. + */ + bool is_null() const { + return operator==( NULL_ID() ); + } + /** + * Same as `!is_null`, basically one can use it to check for the id referring to an actual + * object. This avoids explicitly comparing it with NULL_ID. The id may still be invalid, + * but that should have been checked when the world data was loaded. + * \code + * string_id id = ...; + * if( id ) { + * apply_id( id ); + * } else { + * // was the null-id, ignore it. + * } + * \endcode + */ + explicit operator bool() const { + return !is_null(); + } + + // TODO: Exposed for now. Hide these and make them accessible to the generic_factory only + + /** + * Assigns a new value for the cached int id. + */ + void set_cid( const int_id &cid ) const { + _cid = cid.to_i(); + } + /** + * Returns the current value of cached id + */ + int_id get_cid() const { + return int_id( _cid ); + } + + private: + std::string _id; + mutable int _cid; +}; + +// Support hashing of string based ids by forwarding the hash of the string. +namespace std +{ +template +struct hash< string_id > { + std::size_t operator()( const string_id &v ) const noexcept { + return hash()( v.str() ); + } +}; +} // namespace std + +#endif // CATA_SRC_STRING_ID_H diff --git a/Src/translations.h b/Src/translations.h new file mode 100644 index 0000000000..b035f56f87 --- /dev/null +++ b/Src/translations.h @@ -0,0 +1,296 @@ +#pragma once +#ifndef CATA_SRC_TRANSLATIONS_H +#define CATA_SRC_TRANSLATIONS_H + +#include +#include +#include +#include +#include +#include + +#include "optional.h" + +#if !defined(translate_marker) +/** + * Marks a string literal to be extracted for translation. This is only for running `xgettext` via + * "lang/update_pot.sh". Use `_` to extract *and* translate at run time. The macro itself does not + * do anything, the argument is passed through it without any changes. + */ +#define translate_marker(x) x +#endif +#if !defined(translate_marker_context) +/** + * Same as @ref translate_marker, but also provides a context (string literal). This is similar + * to @ref pgettext, but it does not translate at run time. Like @ref translate_marker it just + * passes the *second* argument through. + */ +#define translate_marker_context(c, x) x +#endif + +#if defined(LOCALIZE) + +// MingW flips out if you don't define this before you try to statically link libintl. +// This should prevent 'undefined reference to `_imp__libintl_gettext`' errors. +#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(_MSC_VER) +# if !defined(LIBINTL_STATIC) +# define LIBINTL_STATIC +# endif +#endif + +// IWYU pragma: begin_exports +#include +// IWYU pragma: end_exports + +#if defined(__GNUC__) +# define ATTRIBUTE_FORMAT_ARG(a) __attribute__((format_arg(a))) +#else +# define ATTRIBUTE_FORMAT_ARG(a) +#endif + +const char *_( const char *msg ) ATTRIBUTE_FORMAT_ARG( 1 ); +inline const char *_( const char *msg ) +{ + return msg[0] == '\0' ? msg : gettext( msg ); +} +inline std::string _( const std::string &msg ) +{ + return _( msg.c_str() ); +} + +// ngettext overload taking an unsigned long long so that people don't need +// to cast at call sites. This is particularly relevant on 64-bit Windows where +// size_t is bigger than unsigned long, so MSVC will try to encourage you to +// add a cast. +template::value>> +ATTRIBUTE_FORMAT_ARG( 1 ) +inline const char *ngettext( const char *msgid, const char *msgid_plural, T n ) +{ + // Leaving this long because it matches the underlying API. + // NOLINTNEXTLINE(cata-no-long) + return ngettext( msgid, msgid_plural, static_cast( n ) ); +} + +const char *pgettext( const char *context, const char *msgid ) ATTRIBUTE_FORMAT_ARG( 2 ); + +// same as pgettext, but supports plural forms like ngettext +const char *npgettext( const char *context, const char *msgid, const char *msgid_plural, + unsigned long long n ) ATTRIBUTE_FORMAT_ARG( 2 ); + +#else // !LOCALIZE + +// on some systems pulls in libintl.h anyway, +// so preemptively include it before the gettext overrides. +#include + +#define _(STRING) (STRING) + +#define ngettext(STRING1, STRING2, COUNT) (COUNT < 2 ? _(STRING1) : _(STRING2)) +#define pgettext(STRING1, STRING2) _(STRING2) +#define npgettext(STRING0, STRING1, STRING2, COUNT) ngettext(STRING1, STRING2, COUNT) + +#endif // LOCALIZE + +using GenderMap = std::map>; +/** + * Translation with a gendered context + * + * Similar to pgettext, but the context is a collection of genders. + * @param genders A map where each key is a subject name (a string which should + * make sense to the translator in the context of the line to be translated) + * and the corresponding value is a list of potential genders for that subject. + * The first gender from the list of genders for the current language will be + * chosen for each subject (or the language default if there are no genders in + * common). + */ +std::string gettext_gendered( const GenderMap &genders, const std::string &msg ); + +bool isValidLanguage( const std::string &lang ); +std::string getLangFromLCID( const int &lcid ); +void select_language(); +void set_language(); + +class JsonIn; + +/** + * Class for storing translation context and raw string for deferred translation + **/ +class translation +{ + public: + struct plural_tag {}; + + translation(); + /** + * Same as `translation()`, but with plural form enabled. + **/ + translation( plural_tag ); + + /** + * Store a string, an optional plural form, and an optional context for translation + **/ + static translation to_translation( const std::string &raw ); + static translation to_translation( const std::string &ctxt, const std::string &raw ); + static translation pl_translation( const std::string &raw, const std::string &raw_pl ); + static translation pl_translation( const std::string &ctxt, const std::string &raw, + const std::string &raw_pl ); + /** + * Store a string that needs no translation. + **/ + static translation no_translation( const std::string &str ); + + /** + * Can be used to ensure a translation object has plural form enabled + * before loading into it from JSON. If plural form has not been enabled + * yet, the plural string will be set to the original singular string. + * `ngettext` will ignore the new plural string and correctly retrieve + * the original translation. + * Note that a `make_singular()` function is not provided due to the + * potential loss of information. + **/ + void make_plural(); + + /** + * Deserialize from json. Json format is: + * "text" + * or + * { "ctxt": "foo", "str": "bar", "str_pl": "baz" } + * "ctxt" and "str_pl" are optional. "str_pl" is only valid when an object + * of this class is constructed with `plural_tag` or `pl_translation()`, + * or converted using `make_plural()`. + **/ + void deserialize( JsonIn &jsin ); + + /** + * Returns raw string if no translation is needed, otherwise returns + * the translated string. A number can be used to translate the plural + * form if the object has it. + **/ + std::string translated( int num = 1 ) const; + + /** + * Methods exposing the underlying raw strings are not implemented, and + * probably should not if there's no good reason to do so. Most importantly, + * the underlying strings should not be re-saved to JSON: doing so risk + * the original string being changed during development and the saved + * string will then not be properly translated when loaded back. If you + * really want to save a translation, translate it early on, store it using + * `no_translation`, and retrieve it using `translated()` when saving. + * This ensures consistent behavior before and after saving and loading. + **/ + std::string untranslated() const = delete; + + /** + * Whether the underlying string is empty, not matter what the context + * is or whether translation is needed. + **/ + bool empty() const; + + /** + * Compare translations by their translated strings (singular form). + * + * Be especially careful when using these to sort translations, as the + * translated result will change when switching the language. + **/ + bool translated_lt( const translation &that ) const; + bool translated_eq( const translation &that ) const; + bool translated_ne( const translation &that ) const; + + /** + * Compare translations by their context, raw strings (singular / plural), and no-translation flag + */ + bool operator==( const translation &that ) const; + bool operator!=( const translation &that ) const; + + /** + * Only used for migrating old snippet hashes into snippet ids. + */ + cata::optional legacy_hash() const; + private: + translation( const std::string &ctxt, const std::string &raw ); + translation( const std::string &raw ); + translation( const std::string &raw, const std::string &raw_pl, plural_tag ); + translation( const std::string &ctxt, const std::string &raw, const std::string &raw_pl, + plural_tag ); + struct no_translation_tag {}; + translation( const std::string &str, no_translation_tag ); + + cata::optional ctxt; + std::string raw; + cata::optional raw_pl; + bool needs_translation = false; +}; + +/** + * Shorthands for translation::to_translation + **/ +translation to_translation( const std::string &raw ); +translation to_translation( const std::string &ctxt, const std::string &raw ); +/** + * Shorthands for translation::pl_translation + **/ +translation pl_translation( const std::string &raw, const std::string &raw_pl ); +translation pl_translation( const std::string &ctxt, const std::string &raw, + const std::string &raw_pl ); +/** + * Shorthand for translation::no_translation + **/ +translation no_translation( const std::string &str ); + +/** + * Stream output and concatenation of translations. Singular forms are used. + **/ +std::ostream &operator<<( std::ostream &out, const translation &t ); +std::string operator+( const translation &lhs, const std::string &rhs ); +std::string operator+( const std::string &lhs, const translation &rhs ); +std::string operator+( const translation &lhs, const translation &rhs ); + +// Localized comparison operator, intended for sorting strings when they should +// be sorted according to the user's locale. +// +// For convenience, it also sorts pairs recursively, because a common +// requirement is to sort some list of objects by their names, and this can be +// achieved by sorting a list of pairs where the first element of the pair is +// the translated name. +struct localized_comparator { + template + bool operator()( const std::pair &l, const std::pair &r ) const { + if( ( *this )( l.first, r.first ) ) { + return true; + } + if( ( *this )( r.first, l.first ) ) { + return false; + } + return ( *this )( l.second, r.second ); + } + + template + bool operator()( const std::tuple &l, + const std::tuple &r ) const { + if( ( *this )( std::get<0>( l ), std::get<0>( r ) ) ) { + return true; + } + if( ( *this )( std::get<0>( r ), std::get<0>( l ) ) ) { + return false; + } + constexpr std::make_index_sequence Ints{}; + return ( *this )( tie_tail( l, Ints ), tie_tail( r, Ints ) ); + } + + template + bool operator()( const T &l, const T &r ) const { + return l < r; + } + + bool operator()( const std::string &, const std::string & ) const; + bool operator()( const std::wstring &, const std::wstring & ) const; + + template + auto tie_tail( const std::tuple &t, std::index_sequence ) const { + return std::tie( std::get < Ints + 1 > ( t )... ); + } +}; + +constexpr localized_comparator localized_compare{}; + +#endif // CATA_SRC_TRANSLATIONS_H diff --git a/Src/units.h b/Src/units.h new file mode 100644 index 0000000000..32dd9c55d3 --- /dev/null +++ b/Src/units.h @@ -0,0 +1,918 @@ +#pragma once +#ifndef CATA_SRC_UNITS_H +#define CATA_SRC_UNITS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compatibility.h" +#include "json.h" +#include "translations.h" + +namespace units +{ + +template +class quantity +{ + public: + using value_type = V; + using unit_type = U; + using this_type = quantity; + + /** + * Create an empty quantity - its @ref value_ is value initialized. + * It does not need an explicitly named unit, it's always 0: 0 l == 0 ml == 0 Ml. + */ + constexpr quantity() : value_() { + } + /** + * Construct from value. This is supposed to be wrapped into a static + * function (e.g. `from_liter(int)` ) to provide context. + */ + constexpr quantity( const value_type &v, unit_type ) : value_( v ) { + } + /** + * Conversion from other value type, e.g. from `quantity` to + * `quantity`. The unit type stays the same! + */ + template + constexpr quantity( const quantity &other ) : value_( other.value() ) { + } + + /** + * Access the raw dimensionless value. Use it in a properly named wrapper function only. + */ + constexpr const value_type &value() const { + return value_; + } + + /** + * The usual comparators, they compare the base value only. + */ + /**@{*/ + constexpr bool operator==( const this_type &rhs ) const { + return value_ == rhs.value_; + } + constexpr bool operator!=( const this_type &rhs ) const { + return !operator==( rhs ); + } + constexpr bool operator<( const this_type &rhs ) const { + return value_ < rhs.value_; + } + constexpr bool operator>=( const this_type &rhs ) const { + return !operator<( rhs ); + } + constexpr bool operator>( const this_type &rhs ) const { + return value_ > rhs.value_; + } + constexpr bool operator<=( const this_type &rhs ) const { + return !operator>( rhs ); + } + /**@}*/ + + /** + * Addition and subtraction of quantities of the same unit type. Result is + * a quantity with the same unit as the input. + * Functions are templated to allow combining quantities with different `value_type`, e.g. + * \code + * quantity a = ...; + * quantity b = ...; + * auto sum = a + b; + * static_assert(std::is_same>::value); + * \endcode + * + * Note that `+=` and `-=` accept any type as `value_type` for the other operand, but + * they convert this back to the type of the right hand, like in `int a; a += 0.4;` + * \code + * quantity a( 10, foo{} ); + * quantity b( 0.5, foo{} ); + * a += b; + * assert( a == quantity( 10 + 0.5, foo{} ) ); + * assert( a == quantity( 10, foo{} ) ); + * \endcode + */ + /**@{*/ + template + constexpr quantity < decltype( std::declval() + std::declval() ), + unit_type > + operator+( const quantity &rhs ) const { + return { value_ + rhs.value(), unit_type{} }; + } + template + constexpr quantity < decltype( std::declval() + std::declval() ), + unit_type > + operator-( const quantity &rhs ) const { + return { value_ - rhs.value(), unit_type{} }; + } + + template + this_type &operator+=( const quantity &rhs ) { + value_ += rhs.value(); + return *this; + } + template + this_type &operator-=( const quantity &rhs ) { + value_ -= rhs.value(); + return *this; + } + /**@}*/ + + constexpr this_type operator-() const { + return this_type( -value_, unit_type{} ); + } + + void serialize( JsonOut &jsout ) const; + void deserialize( JsonIn &jsin ); + + private: + value_type value_; +}; + +/** + * Multiplication and division with scalars. Result is a quantity with the same unit + * as the input. + * Functions are templated to allow scaling with different types: + * \code + * quantity a{ 10, foo{} }; + * auto b = a * 4.52; + * static_assert(std::is_same>::value); + * \endcode + * + * Note that the result for `*=` and `/=` is calculated using the given types, but is + * implicitly converted back to `value_type` as it is stored in the operand. + * \code + * quantity a{ 10, foo{} }; + * a *= 4.52; + * assert( a == quantity( 10 * 4.52, foo{} ) ); + * assert( a != quantity( 10 * (int)4.52, foo{} ) ); + * assert( a == quantity( 45, foo{} ) ); + * \endcode + * + * Division of a quantity with a quantity of the same unit yields a dimensionless + * scalar value, with the same type as the division of the contained `value_type`s: + * \code + * quantity a{ 10, foo{} }; + * quantity b{ 20, foo{} }; + * auto proportion = a / b; + * static_assert(std::is_same::value); + * assert( proportion == 10 / 20.0 ); + * \endcode + * + */ +/**@{*/ +// the decltype in the result type ensures the returned type has the same scalar type +// as you would get when performing the operation directly: +// `int * double` => `double` and `char * int` => `int` +// st is scalar type (dimensionless) +// lvt is the value type (of a quantity) on the left side, rvt is the value type on the right side +// ut is unit type (same for left and right side) +// The enable_if ensures no ambiguity, the compiler may otherwise not be able to decide whether +// "quantity / scalar" or "quantity / other_quanity" is meant. + +// scalar * quantity == quantity +template::value>::type> +inline constexpr quantity() * std::declval() ), ut> +operator*( const st &factor, const quantity &rhs ) +{ + return { factor * rhs.value(), ut{} }; +} + +// same as above only with inverse order of operands: quantity * scalar +template::value>::type> +inline constexpr quantity() * std::declval() ), ut> +operator*( const quantity &lhs, const st &factor ) +{ + return { lhs.value() *factor, ut{} }; +} + +// quantity * quantity is not supported +template::value>::type> +inline void operator*( quantity, quantity ) = delete; + +// operator *= +template::value>::type> +inline quantity & +operator*=( quantity &lhs, const st &factor ) +{ + lhs = lhs * factor; + return lhs; +} + +// and the revers of the multiplication above: +// quantity / scalar == quantity +template::value>::type> +inline constexpr quantity() * std::declval() ), ut> +operator/( const quantity &lhs, const rvt &divisor ) +{ + return { lhs.value() / divisor, ut{} }; +} + +// scalar / quantity is not supported +template::value>::type> +inline void operator/( lvt, quantity ) = delete; + +// quantity / quantity == decltype(foo / bar) +template +inline constexpr decltype( std::declval() / std::declval() ) +operator/( const quantity &lhs, const quantity &rhs ) +{ + return lhs.value() / rhs.value(); +} + +// operator /= +template::value>::type> +inline quantity & +operator/=( quantity &lhs, const st &divisor ) +{ + lhs = lhs / divisor; + return lhs; +} + +// remainder: +// quantity % scalar == quantity +template::value>::type> +inline constexpr quantity < decltype( std::declval() % std::declval() ), ut > +operator%( const quantity &lhs, const rvt &divisor ) +{ + return { lhs.value() % divisor, ut{} }; +} + +// scalar % quantity is not supported +template::value>::type> +inline void operator%( lvt, quantity ) = delete; + +// quantity % quantity == decltype(foo % bar) +template +inline constexpr quantity < decltype( std::declval() % std::declval() ), ut > +operator%( const quantity &lhs, const quantity &rhs ) +{ + return { lhs.value() % rhs.value(), ut{} }; +} + +// operator %= +template::value>::type> +inline quantity & +operator%=( quantity &lhs, const st &divisor ) +{ + lhs = lhs % divisor; + return lhs; +} +template +inline quantity & +operator%=( quantity &lhs, const quantity &rhs ) +{ + lhs = lhs % rhs; + return lhs; +} +/**@}*/ + +class volume_in_milliliter_tag +{ +}; + +using volume = quantity; + +const volume volume_min = units::volume( std::numeric_limits::min(), + units::volume::unit_type{} ); + +const volume volume_max = units::volume( std::numeric_limits::max(), + units::volume::unit_type{} ); + +template +inline constexpr quantity from_milliliter( + const value_type v ) +{ + return quantity( v, volume_in_milliliter_tag{} ); +} + +template +inline constexpr quantity from_liter( const value_type v ) +{ + return from_milliliter( v * 1000 ); +} + +template +inline constexpr value_type to_milliliter( const quantity &v ) +{ + return v / from_milliliter( 1 ); +} + +inline constexpr double to_liter( const volume &v ) +{ + return v.value() / 1000.0; +} + +// Legacy conversions factor for old volume values. +// Don't use in new code! Use one of the from_* functions instead. +static constexpr volume legacy_volume_factor = from_milliliter( 250 ); + +class mass_in_milligram_tag +{ +}; + +using mass = quantity; + +const mass mass_min = units::mass( std::numeric_limits::min(), + units::mass::unit_type{} ); + +const mass mass_max = units::mass( std::numeric_limits::max(), + units::mass::unit_type{} ); + +template +inline constexpr quantity from_milligram( + const value_type v ) +{ + return quantity( v, mass_in_milligram_tag{} ); +} + +template +inline constexpr quantity from_gram( + const value_type v ) +{ + return from_milligram( v * 1000 ); +} + +template +inline constexpr quantity from_kilogram( + const value_type v ) +{ + return from_gram( v * 1000 ); +} + +template +inline constexpr value_type to_milligram( const quantity &v ) +{ + return v.value(); +} + +template +inline constexpr value_type to_gram( const quantity &v ) +{ + return v.value() / 1000.0; +} + +inline constexpr double to_kilogram( const mass &v ) +{ + return v.value() / 1000000.0; +} + +class energy_in_millijoule_tag +{ +}; + +using energy = quantity; + +const energy energy_min = units::energy( std::numeric_limits::min(), + units::energy::unit_type{} ); + +const energy energy_max = units::energy( std::numeric_limits::max(), + units::energy::unit_type{} ); + +template +inline constexpr quantity from_millijoule( + const value_type v ) +{ + return quantity( v, energy_in_millijoule_tag{} ); +} + +template +inline constexpr quantity from_joule( const value_type v ) +{ + const value_type max_energy_joules = std::numeric_limits::max() / 1000; + // Check for overflow - if the energy provided is greater than max energy, then it + // if overflow when converted to millijoules + const value_type energy = v > max_energy_joules ? max_energy_joules : v; + return from_millijoule( energy * 1000 ); +} + +template +inline constexpr quantity from_kilojoule( const value_type v ) +{ + const value_type max_energy_joules = std::numeric_limits::max() / 1000; + // This checks for value_type overflow - if the energy we are given in Joules is greater + // than the max energy in Joules, overflow will occur when it is converted to millijoules + // The value we are given is in kJ, multiply by 1000 to convert it to joules, for use in from_joule + value_type energy = v * 1000 > max_energy_joules ? max_energy_joules : v * 1000; + return from_joule( energy ); +} + +template +inline constexpr value_type to_millijoule( const quantity &v ) +{ + return v / from_millijoule( 1 ); +} + +template +inline constexpr value_type to_joule( const quantity &v ) +{ + return to_millijoule( v ) / 1000.0; +} + +template +inline constexpr value_type to_kilojoule( const quantity &v ) +{ + return to_joule( v ) / 1000.0; +} + +class money_in_cent_tag +{ +}; + +using money = quantity; + +const money money_min = units::money( std::numeric_limits::min(), + units::money::unit_type{} ); + +const money money_max = units::money( std::numeric_limits::max(), + units::money::unit_type{} ); + +template +inline constexpr quantity from_cent( + const value_type v ) +{ + return quantity( v, money_in_cent_tag{} ); +} + +template +inline constexpr quantity from_usd( const value_type v ) +{ + return from_cent( v * 100 ); +} + +template +inline constexpr quantity from_kusd( const value_type v ) +{ + return from_usd( v * 1000 ); +} + +template +inline constexpr value_type to_cent( const quantity &v ) +{ + return v / from_cent( 1 ); +} + +template +inline constexpr value_type to_usd( const quantity &v ) +{ + return to_cent( v ) / 100.0; +} + +template +inline constexpr value_type to_kusd( const quantity &v ) +{ + return to_usd( v ) / 1000.0; +} + +class length_in_millimeter_tag +{ +}; + +using length = quantity; + +const length length_min = units::length( std::numeric_limits::min(), + units::length::unit_type{} ); + +const length length_max = units::length( std::numeric_limits::max(), + units::length::unit_type{} ); + +template +inline constexpr quantity from_millimeter( + const value_type v ) +{ + return quantity( v, length_in_millimeter_tag{} ); +} + +template +inline constexpr quantity from_centimeter( + const value_type v ) +{ + return from_millimeter( v * 10 ); +} + +template +inline constexpr quantity from_meter( + const value_type v ) +{ + return from_millimeter( v * 1000 ); +} + +template +inline constexpr quantity from_kilometer( + const value_type v ) +{ + return from_millimeter( v * 1'000'000 ); +} + +template +inline constexpr value_type to_millimeter( const quantity &v ) +{ + return v / from_millimeter( 1 ); +} + +template +inline constexpr value_type to_centimeter( const quantity &v ) +{ + return to_millimeter( v ) / 10.0; +} + +template +inline constexpr value_type to_meter( const quantity &v ) +{ + return to_millimeter( v ) / 1'000.0; +} + +template +inline constexpr value_type to_kilometer( const quantity &v ) +{ + return to_millimeter( v ) / 1'000'000.0; +} + +// converts a volume as if it were a cube to the length of one side +template +inline constexpr quantity default_length_from_volume( + const quantity &v ) +{ + return units::from_centimeter( + std::round( + std::cbrt( units::to_milliliter( v ) ) ) ); +} + +// Streaming operators for debugging and tests +// (for UI output other functions should be used which render in the user's +// chosen units) +inline std::ostream &operator<<( std::ostream &o, mass_in_milligram_tag ) +{ + return o << "mg"; +} + +inline std::ostream &operator<<( std::ostream &o, volume_in_milliliter_tag ) +{ + return o << "ml"; +} + +inline std::ostream &operator<<( std::ostream &o, energy_in_millijoule_tag ) +{ + return o << "mJ"; +} + +inline std::ostream &operator<<( std::ostream &o, money_in_cent_tag ) +{ + return o << "cent"; +} + +inline std::ostream &operator<<( std::ostream &o, length_in_millimeter_tag ) +{ + return o << "mm"; +} + +template +inline std::ostream &operator<<( std::ostream &o, const quantity &v ) +{ + return o << v.value() << tag_type{}; +} + +template +inline std::string quantity_to_string( const quantity &v ) +{ + std::ostringstream os; + os << v; + return os.str(); +} + +inline std::string display( const units::energy v ) +{ + const int kj = units::to_kilojoule( v ); + const int j = units::to_joule( v ); + // at least 1 kJ and there is no fraction + if( kj >= 1 && static_cast( j ) / kj == 1000 ) { + return to_string( kj ) + ' ' + pgettext( "energy unit: kilojoule", "kJ" ); + } + const int mj = units::to_millijoule( v ); + // at least 1 J and there is no fraction + if( j >= 1 && static_cast( mj ) / j == 1000 ) { + return to_string( j ) + ' ' + pgettext( "energy unit: joule", "J" ); + } + return to_string( mj ) + ' ' + pgettext( "energy unit: millijoule", "mJ" ); +} + +} // namespace units + +// Implicitly converted to volume, which has int as value_type! +inline constexpr units::volume operator"" _ml( const unsigned long long v ) +{ + return units::from_milliliter( v ); +} + +inline constexpr units::quantity operator"" _ml( + const long double v ) +{ + return units::from_milliliter( v ); +} + +// Implicitly converted to volume, which has int as value_type! +inline constexpr units::volume operator"" _liter( const unsigned long long v ) +{ + return units::from_milliliter( v * 1000 ); +} + +inline constexpr units::quantity operator"" _liter( + const long double v ) +{ + return units::from_milliliter( v * 1000 ); +} + +// Implicitly converted to mass, which has int as value_type! +inline constexpr units::mass operator"" _milligram( const unsigned long long v ) +{ + return units::from_milligram( v ); +} +inline constexpr units::mass operator"" _gram( const unsigned long long v ) +{ + return units::from_gram( v ); +} + +inline constexpr units::mass operator"" _kilogram( const unsigned long long v ) +{ + return units::from_kilogram( v ); +} + +inline constexpr units::quantity operator"" _milligram( + const long double v ) +{ + return units::from_milligram( v ); +} + +inline constexpr units::quantity operator"" _gram( + const long double v ) +{ + return units::from_gram( v ); +} + +inline constexpr units::quantity operator"" _kilogram( + const long double v ) +{ + return units::from_kilogram( v ); +} + +inline constexpr units::energy operator"" _mJ( const unsigned long long v ) +{ + return units::from_millijoule( v ); +} + +inline constexpr units::quantity operator"" _mJ( + const long double v ) +{ + return units::from_millijoule( v ); +} + +inline constexpr units::energy operator"" _J( const unsigned long long v ) +{ + return units::from_joule( v ); +} + +inline constexpr units::quantity operator"" _J( + const long double v ) +{ + return units::from_joule( v ); +} + +inline constexpr units::energy operator"" _kJ( const unsigned long long v ) +{ + return units::from_kilojoule( v ); +} + +inline constexpr units::quantity operator"" _kJ( + const long double v ) +{ + return units::from_kilojoule( v ); +} + +inline constexpr units::money operator"" _cent( const unsigned long long v ) +{ + return units::from_cent( v ); +} + +inline constexpr units::quantity operator"" _cent( + const long double v ) +{ + return units::from_cent( v ); +} + +inline constexpr units::money operator"" _USD( const unsigned long long v ) +{ + return units::from_usd( v ); +} + +inline constexpr units::quantity operator"" _USD( + const long double v ) +{ + return units::from_usd( v ); +} + +inline constexpr units::money operator"" _kUSD( const unsigned long long v ) +{ + return units::from_kusd( v ); +} + +inline constexpr units::quantity operator"" _kUSD( + const long double v ) +{ + return units::from_kusd( v ); +} + +inline constexpr units::quantity operator"" _mm( + const long double v ) +{ + return units::from_millimeter( v ); +} + +inline constexpr units::length operator"" _mm( const unsigned long long v ) +{ + return units::from_millimeter( v ); +} + +inline constexpr units::quantity operator"" _cm( + const long double v ) +{ + return units::from_centimeter( v ); +} + +inline constexpr units::length operator"" _cm( const unsigned long long v ) +{ + return units::from_centimeter( v ); +} + +inline constexpr units::quantity operator"" _meter( + const long double v ) +{ + return units::from_meter( v ); +} + +inline constexpr units::length operator"" _meter( const unsigned long long v ) +{ + return units::from_meter( v ); +} + +inline constexpr units::quantity operator"" _km( + const long double v ) +{ + return units::from_kilometer( v ); +} + +inline constexpr units::length operator"" _km( const unsigned long long v ) +{ + return units::from_kilometer( v ); +} + +namespace units +{ +static const std::vector> energy_units = { { + { "mJ", 1_mJ }, + { "J", 1_J }, + { "kJ", 1_kJ }, + } +}; +static const std::vector> mass_units = { { + { "mg", 1_milligram }, + { "g", 1_gram }, + { "kg", 1_kilogram }, + } +}; +static const std::vector> money_units = { { + { "cent", 1_cent }, + { "USD", 1_USD }, + { "kUSD", 1_kUSD }, + } +}; +static const std::vector> volume_units = { { + { "ml", 1_ml }, + { "L", 1_liter } + } +}; +static const std::vector> length_units = { { + { "mm", 1_mm }, + { "cm", 1_cm }, + { "meter", 1_meter }, + { "km", 1_km } + } +}; +} // namespace units + +template +T read_from_json_string( JsonIn &jsin, const std::vector> &units ) +{ + const size_t pos = jsin.tell(); + size_t i = 0; + const auto error = [&]( const char *const msg ) { + jsin.seek( pos + i ); + jsin.error( msg ); + }; + + const std::string s = jsin.get_string(); + // returns whether we are at the end of the string + const auto skip_spaces = [&]() { + while( i < s.size() && s[i] == ' ' ) { + ++i; + } + return i >= s.size(); + }; + const auto get_unit = [&]() { + if( skip_spaces() ) { + error( "invalid quantity string: missing unit" ); + } + for( const auto &pair : units ) { + const std::string &unit = pair.first; + if( s.size() >= unit.size() + i && s.compare( i, unit.size(), unit ) == 0 ) { + i += unit.size(); + return pair.second; + } + } + error( "invalid quantity string: unknown unit" ); + // above always throws but lambdas cannot be marked [[noreturn]] + throw std::string( "Exceptionally impossible" ); + }; + + if( skip_spaces() ) { + error( "invalid quantity string: empty string" ); + } + T result{}; + do { + int sign_value = +1; + if( s[i] == '-' ) { + sign_value = -1; + ++i; + } else if( s[i] == '+' ) { + ++i; + } + if( i >= s.size() || !isdigit( s[i] ) ) { + error( "invalid quantity string: number expected" ); + } + int value = 0; + for( ; i < s.size() && isdigit( s[i] ); ++i ) { + value = value * 10 + ( s[i] - '0' ); + } + result += sign_value * value * get_unit(); + } while( !skip_spaces() ); + return result; +} + +template +void dump_to_json_string( T t, JsonOut &jsout, + const std::vector> &units ) +{ + // deduplicate unit strings and choose the shortest representations + std::map sorted_units; + for( const auto &p : units ) { + const auto it = sorted_units.find( p.second ); + if( it != sorted_units.end() ) { + if( p.first.length() < it->second.length() ) { + it->second = p.first; + } + } else { + sorted_units.emplace( p.second, p.first ); + } + } + std::string str; + bool written = false; + for( auto it = sorted_units.rbegin(); it != sorted_units.rend(); ++it ) { + const int val = static_cast( t / it->first ); + if( val != 0 ) { + if( written ) { + str += ' '; + } + int tmp = val; + if( tmp < 0 ) { + str += '-'; + tmp = -tmp; + } + const size_t val_beg = str.size(); + while( tmp != 0 ) { + str += static_cast( '0' + tmp % 10 ); + tmp /= 10; + } + std::reverse( str.begin() + val_beg, str.end() ); + str += ' '; + str += it->second; + written = true; + t -= it->first * val; + } + } + if( str.empty() ) { + str = "0 " + sorted_units.begin()->second; + } + jsout.write( str ); +} + +#endif // CATA_SRC_UNITS_H diff --git a/Tools/format/CMakeLists.txt b/Tools/format/CMakeLists.txt new file mode 100644 index 0000000000..465eeaae64 --- /dev/null +++ b/Tools/format/CMakeLists.txt @@ -0,0 +1,11 @@ +include(ExternalProject) + +add_executable( + json_formatter + format.cpp + ${CMAKE_SOURCE_DIR}/src/json.cpp + ) + +ADD_DEFINITIONS(-DCATA_IN_TOOL) + +INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src ) diff --git a/Tools/format/format.cpp b/Tools/format/format.cpp new file mode 100644 index 0000000000..69c9f99fc7 --- /dev/null +++ b/Tools/format/format.cpp @@ -0,0 +1,239 @@ +#include "json.h" + +#include "getpost.h" + +#if defined(_MSC_VER) +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include + +static void format( JsonIn &jsin, JsonOut &jsout, int depth = -1, bool force_wrap = false ); + +#if defined(MSYS2) || defined(_MSC_VER) +static void erase_char( std::string &s, const char &c ) +{ + size_t pos = std::string::npos; + while( ( pos = s.find( c ) ) != std::string::npos ) { + s.erase( pos, 1 ); + } +} +#endif + +static void write_array( JsonIn &jsin, JsonOut &jsout, int depth, bool force_wrap ) +{ + jsout.start_array( force_wrap ); + jsin.start_array(); + while( !jsin.end_array() ) { + format( jsin, jsout, depth ); + } + jsout.end_array(); +} + +static void write_object( JsonIn &jsin, JsonOut &jsout, int depth, bool force_wrap ) +{ + jsout.start_object( force_wrap ); + jsin.start_object(); + while( !jsin.end_object() ) { + std::string name = jsin.get_member_name(); + jsout.member( name ); + bool override_wrap = false; + if( name == "rows" || name == "blueprint" ) { + // Introspect into the row, if it has more than one element, force it to wrap. + int in_start_pos = jsin.tell(); + bool ate_seperator = jsin.get_ate_separator(); + { + JsonArray arr = jsin.get_array(); + if( arr.size() > 1 ) { + override_wrap = true; + } + } + jsin.seek( in_start_pos ); + jsin.set_ate_separator( ate_seperator ); + } + format( jsin, jsout, depth, override_wrap ); + } + jsout.end_object(); +} + +static void format_collection( JsonIn &jsin, JsonOut &jsout, int depth, + const std::function &write_func, + bool force_wrap ) +{ + if( depth > 1 && !force_wrap ) { + // We're backtracking by storing jsin and jsout state before formatting + // and restoring it afterwards if necessary. + int in_start_pos = jsin.tell(); + bool ate_seperator = jsin.get_ate_separator(); + int out_start_pos = jsout.tell(); + bool need_separator = jsout.get_need_separator(); + write_func( jsin, jsout, depth, false ); + if( jsout.tell() - out_start_pos <= 120 ) { + // Line is short enough, so we're done. + return; + } else { + // Reset jsin and jsout to their initial state, + // and we'll serialize while forcing wrapping. + jsin.seek( in_start_pos ); + jsin.set_ate_separator( ate_seperator ); + jsout.seek( out_start_pos ); + if( need_separator ) { + jsout.set_need_separator(); + } + } + } + write_func( jsin, jsout, depth, true ); +} + +static void format( JsonIn &jsin, JsonOut &jsout, int depth, bool force_wrap ) +{ + depth++; + if( jsin.test_array() ) { + format_collection( jsin, jsout, depth, write_array, force_wrap ); + } else if( jsin.test_object() ) { + format_collection( jsin, jsout, depth, write_object, force_wrap ); + } else if( jsin.test_string() ) { + // The string may contain escape sequences which we want to keep in the output. + const int start_pos = jsin.tell(); + jsin.get_string(); + const int end_pos = jsin.tell(); + std::string str = jsin.substr( start_pos, end_pos - start_pos ); + str = str.substr( str.find( '"' ) ); + str = str.substr( 0, str.rfind( '"' ) + 1 ); + jsout.write_separator(); + *jsout.get_stream() << str; + jsout.set_need_separator(); + } else if( jsin.test_number() ) { + // Have to introspect into the string to distinguish integers from floats. + // Otherwise they won't serialize correctly. + const int start_pos = jsin.tell(); + jsin.skip_number(); + const int end_pos = jsin.tell(); + std::string str_form = jsin.substr( start_pos, end_pos - start_pos ); + jsin.seek( start_pos ); + if( str_form.find( '.' ) == std::string::npos ) { + if( str_form.find( '-' ) == std::string::npos ) { + const uint64_t num = jsin.get_uint64(); + jsout.write( num ); + } else { + const int64_t num = jsin.get_int64(); + jsout.write( num ); + } + } else { + const double num = jsin.get_float(); + // This is QUITE insane, but as far as I can tell there is NO way to configure + // an ostream to output a float/double meeting two constraints: + // Always emit a decimal point (and single trailing 0 after a bare decimal point). + // Don't emit trailing zeroes otherwise. + std::string double_str = std::to_string( num ); + double_str.erase( double_str.find_last_not_of( '0' ) + 1, std::string::npos ); + if( double_str.back() == '.' ) { + double_str += "0"; + } + jsout.write_separator(); + *jsout.get_stream() << double_str; + jsout.set_need_separator(); + } + } else if( jsin.test_bool() ) { + bool tf = jsin.get_bool(); + jsout.write( tf ); + } else if( jsin.test_null() ) { + jsin.skip_null(); + jsout.write_null(); + } else { + std::cerr << "Encountered unrecognized json element \""; + const int start_pos = jsin.tell(); + jsin.skip_value(); + const int end_pos = jsin.tell(); + for( int i = start_pos; i < end_pos; ++i ) { + jsin.seek( i ); + std::cerr << jsin.peek(); + } + std::cerr << "\"" << std::endl; + } +} + +int main( int argc, char *argv[] ) +{ + std::stringstream in; + std::stringstream out; + std::string filename; + std::string header; + + char *gateway_var = getenv( "GATEWAY_INTERFACE" ); + if( gateway_var == nullptr ) { + // Expect a single filename for now. + if( argc == 2 ) { + filename = argv[1]; + } else if( argc != 1 ) { + std::cout << "Supply a filename to style or no arguments." << std::endl; + exit( EXIT_FAILURE ); + } + + if( filename.empty() ) { + in << std::cin.rdbuf(); + } else { + std::ifstream fin( filename, std::ios::binary ); + if( !fin.good() ) { + std::cout << "Failed to open " << filename << std::endl; + exit( EXIT_FAILURE ); + } + in << fin.rdbuf(); + fin.close(); + } + } else { + std::map params; + initializePost( params ); + std::string data = params[ "data" ]; + if( data.empty() ) { + exit( -255 ); + } + in.str( data ); + header = "Content-type: application/json\n\n"; + } + + if( in.str().empty() ) { + std::cout << "Error, input empty." << std::endl; + exit( EXIT_FAILURE ); + } + JsonOut jsout( out, true ); + JsonIn jsin( in ); + + format( jsin, jsout ); + + out << std::endl; + + if( filename.empty() ) { + std::cout << header; + std::cout << out.str(); + } else { + std::string in_str = in.str(); +#if defined(MSYS2) || defined(_MSC_VER) + erase_char( in_str, '\r' ); +#endif + +#if defined(_MSC_VER) + bool supports_color = _isatty( _fileno( stdout ) ); +#else + bool supports_color = isatty( STDOUT_FILENO ); +#endif + std::string color_bad = supports_color ? "\x1b[31m" : std::string(); + std::string color_end = supports_color ? "\x1b[0m" : std::string(); + if( in_str == out.str() ) { + exit( EXIT_SUCCESS ); + } else { + std::ofstream fout( filename, std::ios::binary | std::ios::trunc ); + fout << out.str(); + fout.close(); + std::cout << color_bad << "Needs linting : " << color_end << filename << std::endl; + std::cout << "Please read doc/JSON_STYLE.md" << std::endl; + exit( EXIT_FAILURE ); + } + } +} diff --git a/Tools/format/format.h b/Tools/format/format.h new file mode 100644 index 0000000000..9991bc3cb3 --- /dev/null +++ b/Tools/format/format.h @@ -0,0 +1,12 @@ +#ifndef CATA_TOOLS_FORMAT_H +#define CATA_TOOLS_FORMAT_H + +class JsonIn; +class JsonOut; + +namespace formatter +{ +void format( JsonIn &jsin, JsonOut &jsout, int depth = -1, bool force_wrap = false ); +} + +#endif diff --git a/Tools/format/format.html b/Tools/format/format.html new file mode 100644 index 0000000000..f673537392 --- /dev/null +++ b/Tools/format/format.html @@ -0,0 +1,119 @@ + + + + Cataclysm: DDA Web JSON Linting Tool + + + + + + +

Cataclysm: Dark Days Ahead JSON Web Linting Tool

+

This is a tool to help modders and editors of the open source game Cataclysm: Dark Days Ahead write JSON in the game's expected format.

+

Paste some JSON into the field below and click "Lint" to run an autoformatter against it.

+
+ + +
+ + diff --git a/Tools/format/format_main.cpp b/Tools/format/format_main.cpp new file mode 100644 index 0000000000..1cfd92e54f --- /dev/null +++ b/Tools/format/format_main.cpp @@ -0,0 +1,93 @@ +#include "format.h" +#include "json.h" + +#include "getpost.h" + +#if defined(MSYS2) || defined(_MSC_VER) +static void erase_char( std::string &s, const char &c ) +{ + size_t pos = std::string::npos; + while( ( pos = s.find( c ) ) != std::string::npos ) { + s.erase( pos, 1 ); + } +} +#endif + +int main( int argc, char *argv[] ) +{ + std::stringstream in; + std::stringstream out; + std::string filename; + std::string header; + + char *gateway_var = getenv( "GATEWAY_INTERFACE" ); + if( gateway_var == nullptr ) { + // Expect a single filename for now. + if( argc == 2 ) { + filename = argv[1]; + } else if( argc != 1 ) { + std::cout << "Supply a filename to style or no arguments." << std::endl; + exit( EXIT_FAILURE ); + } + + if( filename.empty() ) { + in << std::cin.rdbuf(); + } else { + std::ifstream fin( filename, std::ios::binary ); + if( !fin.good() ) { + std::cout << "Failed to open " << filename << std::endl; + exit( EXIT_FAILURE ); + } + in << fin.rdbuf(); + fin.close(); + } + } else { + std::map params; + initializePost( params ); + std::string data = params[ "data" ]; + if( data.empty() ) { + exit( -255 ); + } + in.str( data ); + header = "Content-type: application/json\n\n"; + } + + if( in.str().empty() ) { + std::cout << "Error, input empty." << std::endl; + exit( EXIT_FAILURE ); + } + JsonOut jsout( out, true ); + JsonIn jsin( in ); + + formatter::format( jsin, jsout ); + + out << std::endl; + + if( filename.empty() ) { + std::cout << header; + std::cout << out.str(); + } else { + std::string in_str = in.str(); +#if defined(MSYS2) || defined(_MSC_VER) + erase_char( in_str, '\r' ); +#endif + +#if defined(_MSC_VER) + bool supports_color = _isatty( _fileno( stdout ) ); +#else + bool supports_color = isatty( STDOUT_FILENO ); +#endif + std::string color_bad = supports_color ? "\x1b[31m" : std::string(); + std::string color_end = supports_color ? "\x1b[0m" : std::string(); + if( in_str == out.str() ) { + exit( EXIT_SUCCESS ); + } else { + std::ofstream fout( filename, std::ios::binary | std::ios::trunc ); + fout << out.str(); + fout.close(); + std::cout << color_bad << "Needs linting : " << color_end << filename << std::endl; + std::cout << "Please read doc/JSON_STYLE.md" << std::endl; + exit( EXIT_FAILURE ); + } + } +} diff --git a/Tools/format/getpost.h b/Tools/format/getpost.h new file mode 100644 index 0000000000..6f33c71882 --- /dev/null +++ b/Tools/format/getpost.h @@ -0,0 +1,149 @@ +/***************************************************************************** +The MIT License + +Copyright (c) 2007 Guy Rutenberg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*****************************************************************************/ + +#ifndef __GETPOST_H__ +#define __GETPOST_H__ + +#include +#include +#include +#include +#include + +inline std::string urlDecode( std::string str ) +{ + std::string temp; + int i; + char tmp[5], tmpchar; + strcpy( tmp, "0x" ); + int size = str.size(); + for( i = 0; i < size; i++ ) { + if( str[i] == '%' ) { + if( i + 2 < size ) { + tmp[2] = str[i + 1]; + tmp[3] = str[i + 2]; + tmp[4] = '\0'; + tmpchar = static_cast( strtol( tmp, nullptr, 0 ) ); + temp += tmpchar; + i += 2; + continue; + } else { + break; + } + } else if( str[i] == '+' ) { + temp += ' '; + } else { + temp += str[i]; + } + } + return temp; +} + +inline void initializeGet( std::map &Get ) +{ + std::string tmpkey, tmpvalue; + std::string *tmpstr = &tmpkey; + char *raw_get = getenv( "QUERY_STRING" ); + if( raw_get == nullptr ) { + Get.clear(); + return; + } + while( *raw_get != '\0' ) { + if( *raw_get == '&' ) { + if( !tmpkey.empty() ) { + Get[urlDecode( tmpkey )] = urlDecode( tmpvalue ); + } + tmpkey.clear(); + tmpvalue.clear(); + tmpstr = &tmpkey; + } else if( *raw_get == '=' ) { + tmpstr = &tmpvalue; + } else { + ( *tmpstr ) += ( *raw_get ); + } + raw_get++; + } + //enter the last pair to the map + if( !tmpkey.empty() ) { + Get[urlDecode( tmpkey )] = urlDecode( tmpvalue ); + tmpkey.clear(); + tmpvalue.clear(); + } +} + +inline void initializePost( std::map &Post ) +{ + std::string tmpkey, tmpvalue; + std::string *tmpstr = &tmpkey; + int content_length; + char *ibuffer; + char *buffer = nullptr; + char *strlength = getenv( "CONTENT_LENGTH" ); + if( strlength == nullptr ) { + Post.clear(); + return; + } + content_length = atoi( strlength ); + if( content_length == 0 ) { + Post.clear(); + return; + } + + try { + buffer = new char[content_length * sizeof( char )]; + } catch( std::bad_alloc & ) { + Post.clear(); + return; + } + if( fread( buffer, sizeof( char ), content_length, + stdin ) != static_cast( content_length ) ) { + Post.clear(); + return; + } + *( buffer + content_length ) = '\0'; + ibuffer = buffer; + while( *ibuffer != '\0' ) { + if( *ibuffer == '&' ) { + if( !tmpkey.empty() ) { + Post[urlDecode( tmpkey )] = urlDecode( tmpvalue ); + } + tmpkey.clear(); + tmpvalue.clear(); + tmpstr = &tmpkey; + } else if( *ibuffer == '=' ) { + tmpstr = &tmpvalue; + } else { + ( *tmpstr ) += ( *ibuffer ); + } + ibuffer++; + } + //enter the last pair to the map + if( !tmpkey.empty() ) { + Post[urlDecode( tmpkey )] = urlDecode( tmpvalue ); + tmpkey.clear(); + tmpvalue.clear(); + } +} + +#endif /*__GETPOST_H__*/ diff --git a/Tools/lint-json.sh b/Tools/lint-json.sh new file mode 100644 index 0000000000..6a92a9d675 --- /dev/null +++ b/Tools/lint-json.sh @@ -0,0 +1 @@ +find . -name "*json" -type f -exec python3 -m json.tool {} >/dev/null \; diff --git a/Tools/pl_script.py b/Tools/pl_script.py new file mode 100644 index 0000000000..747c9bfa54 --- /dev/null +++ b/Tools/pl_script.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +import argparse +import json +import os + +args = argparse.ArgumentParser() +args.add_argument("dir", action="store", help="specify json directory") +args_dict = vars(args.parse_args()) + + +def gen_new(path): + change = False + with open(path, "r") as json_file: + json_data = json.load(json_file) + for jo in json_data: + # We only want JsonObjects + if type(jo) is str: + return None + + if "str_pl" in jo and "name" in jo and not type(jo["name"]) is str: + if jo["name"]["str"] == jo["str_pl"]: + jo["name"]["str_sp"] = jo["name"]["str"] + del jo["name"]["str"] + del jo["str_pl"] + else: + name_obj = {} + name_obj["str"] = jo["name"]["str"] + name_obj["str_pl"] = jo["str_pl"] + del jo["str_pl"] + jo["name"] = name_obj + change = True + + return json_data if change else None + + +for root, directories, filenames in os.walk(args_dict["dir"]): + for filename in filenames: + path = os.path.join(root, filename) + if path.endswith(".json"): + new = gen_new(path) + if new is not None: + with open(path, "w") as jf: + json.dump(new, jf, ensure_ascii=False) + os.system(f"./tools/format/json_formatter.cgi {path}") diff --git a/Tools/recipe extractor and editor.py b/Tools/recipe extractor and editor.py new file mode 100644 index 0000000000..3339c65e67 --- /dev/null +++ b/Tools/recipe extractor and editor.py @@ -0,0 +1,92 @@ +import json + +######################################## +# This script copies dictionary that contains "sewing_kit" value in a nested list +# appends it to a new list of dictionaries, changes said value, its "time" value and adds "id_suffix" item. +# Made for extracting and editing armors to use the sewing_machine item +####################################### + + + +# takes care of pening and closing the file, converting it to Python's data type and stores it in "data" opbject +with open("arms.json", "r") as recipejson: + dataList = json.load(recipejson) + + +storageData = [] + + +#moves the dicts that have SEW into a new list +def appender(lis): + for dict in lis: + for keyDict, valueList in dict.items(): + #checks if the value is iterable + if hasattr(keyDict, '__iter__'): + if keyDict == "qualities": + print("found qualities") + for subDict in valueList: + if hasattr(subDict, '__iter__'): + for key, value in subDict.items(): + if value == "SEW": + storageData.append(dict) + print("dictionary moved") + else: + print("key checked") + +def increaseSEW(dataList): + # increases the SEW value + for dict in dataList: + for keyDict, valueList in dict.items(): + # checks if the value is iterable + if hasattr(keyDict, '__iter__'): + if keyDict == "qualities": + print("found it!") + for subDict in valueList: + if hasattr(subDict, '__iter__'): + for key, value in enumerate(subDict): + if value == "level": + print("increased SEW level") + subDict[value] = 2 + else: + print("key checked") + +def decreaseTime(dataList): + # increases the SEW value + for dict in dataList: + for key, value in dict.items(): + # checks if the value is iterable + if hasattr(key, '__iter__'): + if key == "time": + print("found it!") + dict[key] -= int(value * 0.5) + else: + print("key checked") +def changeSubcategory(dataList): + # increases the SEW value + for dict in dataList: + for key, value in dict.items(): + # checks if the value is iterable + if hasattr(key, '__iter__'): + if key == "subcategory": + print("found it!") + dict[key] = "CSC_SEWING_MACHINE" + else: + print("key checked") + + +def addSufix(dataList): + for dict in dataList: + # adds "sm" sufix + dict["id_suffix"] = "sm" + print("sufix key/value added") + +appender(dataList) +increaseSEW(storageData) +decreaseTime(storageData) +changeSubcategory(storageData) +addSufix(storageData) + + +# converts the "data" object to JSON type and writes it on "newRecipe.json" +with open("sm_arms.json", "w") as newRecipe: + json.dump(storageData, newRecipe, sort_keys=True)