diff --git a/external/random.hpp b/external/random.hpp new file mode 100644 index 000000000..77463633f --- /dev/null +++ b/external/random.hpp @@ -0,0 +1,1287 @@ +/* +______ ___ _ _______ ________ __ +| ___ \/ _ \ | \ | | _ \ _ | \/ | Random for modern C++ +| |_/ / /_\ \| \| | | | | | | | . . | +| /| _ || . ` | | | | | | | |\/| | version 1.3.1 +| |\ \| | | || |\ | |/ /\ \_/ / | | | +\_| \_\_| |_/\_| \_/___/ \___/\_| |_/ https://github.com/effolkronium/random + +Licensed under the MIT License . +Copyright (c) 2019 effolkronium + +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 EFFOLKRONIUM_RANDOM_HPP +#define EFFOLKRONIUM_RANDOM_HPP + +#include +#include // timed seed +#include +#include +#include +#include // std::forward, std::declval +#include // std::shuffle, std::next, std::distance +#include // std::begin, std::end, std::iterator_traits +#include // std::numeric_limits +#include +#include + +namespace effolkronium { + + namespace details { + /// Key type for getting common type numbers or objects + struct common{ }; + + /// True if type T is applicable by a std::uniform_int_distribution + template + struct is_uniform_int { + static constexpr bool value = + std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value; + }; + + /// True if type T is applicable by a std::uniform_real_distribution + template + struct is_uniform_real { + static constexpr bool value = + std::is_same::value + || std::is_same::value + || std::is_same::value; + }; + + /// True if type T is plain byte + template + struct is_byte { + static constexpr bool value = + std::is_same::value + || std::is_same::value; + }; + + /// True if type T is plain number type + template + struct is_supported_number { + static constexpr bool value = + is_byte ::value + || is_uniform_real::value + || is_uniform_int ::value; + }; + + /// True if type T is character type + template + struct is_supported_character { + static constexpr bool value = + std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value; + }; + + /// True if type T is iterator + template + struct is_iterator { + private: + static char test( ... ); + + template ::difference_type, + typename = typename std::iterator_traits::pointer, + typename = typename std::iterator_traits::reference, + typename = typename std::iterator_traits::value_type, + typename = typename std::iterator_traits::iterator_category + > static long test( U&& ); + public: + static constexpr bool value = std::is_same< + decltype( test( std::declval( ) ) ), long>::value; + }; + + } // namespace details + + /// Default seeder for 'random' classes + struct seeder_default { + /// return seed sequence + std::seed_seq& operator() ( ) { + // MinGW issue, std::random_device returns constant value + // Use std::seed_seq with additional seed from C++ chrono + return seed_seq; + } + private: + std::seed_seq seed_seq{ { + static_cast( std::random_device{ }( ) ), + static_cast( std::chrono::steady_clock::now( ) + .time_since_epoch( ).count( ) ), + } }; + }; + + /** + * \brief Base template class for random + * with static API and static internal member storage + * \note it is NOT thread safe but more efficient then + * basic_random_thread_local + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ + template< + typename Engine, + typename Seeder = seeder_default, + template class IntegerDist = std::uniform_int_distribution, + template class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution + > + class basic_random_static { + public: + basic_random_static( ) = delete; + + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template + using integer_dist_t = IntegerDist; + + /// Type of used real distribution + template + using real_dist_t = RealDist; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min( ) { + return Engine::min( ); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max( ) { + return Engine::max( ); + } + + /// Advances the internal state by z times + static void discard( const unsigned long long z ) { + engine_instance( ).discard( z ); + } + + /// Reseed by Seeder + static void reseed( ) { + Seeder seeder; + seed( seeder( ) ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + static void seed( const typename Engine::result_type value = + Engine::default_seed ) { + engine_instance( ).seed( value ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template + static void seed( Sseq& seq ) { + engine_instance( ).seed( seq ); + } + + /// return random number from engine in [min(), max()] range + static typename Engine::result_type get( ) { + return engine_instance( )( ); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + static bool is_equal( const Engine& other ) { + return engine_instance( ) == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template + static void serialize( std::basic_ostream& ost ) { + ost << engine_instance( ); + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template + static void deserialize( std::basic_istream& ist ) { + ist >> engine_instance( ); + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return IntegerDist{ from, to }( engine_instance( ) ); + return IntegerDist{ to, from }( engine_instance( ) ); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return RealDist{ from, to }( engine_instance( ) ); + return RealDist{ to, from }( engine_instance( ) ); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional::value, + short, unsigned short>::type; + + return static_cast( get( from, to ) ); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template< + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type + > + static typename std::enable_if< + std::is_same::value + && details::is_supported_number::value + && details::is_supported_number::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed::value != std::is_unsigned::value + , C>::type get( A from = std::numeric_limits::min( ), + B to = std::numeric_limits::max( ) ) { + return get( static_cast( from ), static_cast( to ) ); + } + + /** + * \brief Generate a random character in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random character in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) { + if (from < to) // Allow range from higher to lower + return static_cast(IntegerDist{ static_cast(from), static_cast(to) }(engine_instance())); + return static_cast(IntegerDist{ static_cast(to), static_cast(from) }(engine_instance())); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template + static typename std::enable_if::value + , bool>::type get( const double probability = 0.5 ) { + assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range + return BoolDist{ probability }( engine_instance( ) ); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template + static T get( std::initializer_list init_list ) { + assert( 0u != init_list.size( ) ); + return *get( init_list.begin( ), init_list.end( ) ); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template + static typename std::enable_if::value + , InputIt>::type get( InputIt first, InputIt last ) { + const auto size = std::distance( first, last ); + if( 0 == size ) return last; + using diff_t = typename std::iterator_traits::difference_type; + return std::next( first, get( 0, size - 1 ) ); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template + static auto get( Container& container ) -> + typename std::enable_if::value + , decltype(std::begin(container)) + >::type { + return get( std::begin( container ), std::end( container ) ); + } + + /** + * \brief Return random pointer from built-in array + * \param array The built-in array with elements + * \return Pointer to random element in array + */ + template + static T* get( T( &array )[ N ] ) { + return std::addressof( array[ get( 0, N - 1 ) ] ); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template + static typename Dist::result_type get( Args&&... args ) { + return Dist{ std::forward( args )... }( engine_instance( ) ); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template + static typename Dist::result_type get( Dist& dist ) { + return dist( engine_instance( ) ); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template + static void shuffle( RandomIt first, RandomIt last ) { + std::shuffle( first, last, engine_instance( ) ); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template + static void shuffle( Container& container ) { + shuffle( std::begin( container ), std::end( container ) ); + } + + /// return internal engine by copy + static Engine get_engine( ) { + return engine_instance( ); + } + + /// return internal engine by ref + static Engine& engine() { + return engine_instance(); + } + protected: + /// get reference to the static engine instance + static Engine& engine_instance( ) { + static Engine engine{ Seeder{ }( ) }; + return engine; + } + }; + + /** + * \brief Base template class for random + * with thread_local API and thread_local internal member storage + * \note it IS thread safe but less efficient then + * basic_random_static + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ + template< + typename Engine, + typename Seeder = seeder_default, + template class IntegerDist = std::uniform_int_distribution, + template class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution + > + class basic_random_thread_local { + public: + basic_random_thread_local( ) = delete; + + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template + using integer_dist_t = IntegerDist; + + /// Type of used real distribution + template + using real_dist_t = RealDist; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min( ) { + return Engine::min( ); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max( ) { + return Engine::max( ); + } + + /// Advances the internal state by z times + static void discard( const unsigned long long z ) { + engine_instance( ).discard( z ); + } + + /// Reseed by Seeder + static void reseed( ) { + Seeder seeder; + seed( seeder( ) ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + static void seed( const typename Engine::result_type value = + Engine::default_seed ) { + engine_instance( ).seed( value ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template + static void seed( Sseq& seq ) { + engine_instance( ).seed( seq ); + } + + /// return random number from engine in [min(), max()] range + static typename Engine::result_type get( ) { + return engine_instance( )( ); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + static bool is_equal( const Engine& other ) { + return engine_instance( ) == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template + static void serialize( std::basic_ostream& ost ) { + ost << engine_instance( ); + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template + static void deserialize( std::basic_istream& ist ) { + ist >> engine_instance( ); + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return IntegerDist{ from, to }( engine_instance( ) ); + return IntegerDist{ to, from }( engine_instance( ) ); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return RealDist{ from, to }( engine_instance( ) ); + return RealDist{ to, from }( engine_instance( ) ); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional::value, + short, unsigned short>::type; + + return static_cast( get( from, to ) ); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template< + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type + > + static typename std::enable_if< + std::is_same::value + && details::is_supported_number::value + && details::is_supported_number::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed::value != std::is_unsigned::value + , C>::type get( A from = std::numeric_limits::min( ), + B to = std::numeric_limits::max( ) ) { + return get( static_cast( from ), static_cast( to ) ); + } + + /** + * \brief Generate a random character in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random character in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) { + if (from < to) // Allow range from higher to lower + return static_cast(IntegerDist{ static_cast(from), static_cast(to) }(engine_instance())); + return static_cast(IntegerDist{ static_cast(to), static_cast(from) }(engine_instance())); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template + static typename std::enable_if::value + , bool>::type get( const double probability = 0.5 ) { + assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range + return BoolDist{ probability }( engine_instance( ) ); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template + static T get( std::initializer_list init_list ) { + assert( 0u != init_list.size( ) ); + return *get( init_list.begin( ), init_list.end( ) ); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template + static typename std::enable_if::value + , InputIt>::type get( InputIt first, InputIt last ) { + const auto size = std::distance( first, last ); + if( 0 == size ) return last; + using diff_t = typename std::iterator_traits::difference_type; + return std::next( first, get( 0, size - 1 ) ); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template + static auto get( Container& container ) -> + typename std::enable_if::value + , decltype(std::begin(container)) + >::type { + return get( std::begin( container ), std::end( container ) ); + } + + /** + * \brief Return random pointer from built-in array + * \param array The built-in array with elements + * \return Pointer to random element in array + */ + template + static T* get( T( &array )[ N ] ) { + return std::addressof( array[ get( 0, N - 1 ) ] ); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template + static typename Dist::result_type get( Args&&... args ) { + return Dist{ std::forward( args )... }( engine_instance( ) ); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template + static typename Dist::result_type get( Dist& dist ) { + return dist( engine_instance( ) ); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template + static void shuffle( RandomIt first, RandomIt last ) { + std::shuffle( first, last, engine_instance( ) ); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template + static void shuffle( Container& container ) { + shuffle( std::begin( container ), std::end( container ) ); + } + + /// return internal engine by copy + static Engine get_engine( ) { + return engine_instance( ); + } + + /// return internal engine by ref + static Engine& engine() { + return engine_instance(); + } + protected: + /// get reference to the thread local engine instance + static Engine& engine_instance( ) { + thread_local Engine engine{ Seeder{ }( ) }; + return engine; + } + }; + + /** + * \brief Base template class for random + * with local API and local internal member storage + * \note it IS thread safe but less efficient then + * basic_random_static + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ + template< + typename Engine, + typename Seeder = seeder_default, + template class IntegerDist = std::uniform_int_distribution, + template class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution + > + class basic_random_local { + public: + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template + using integer_dist_t = IntegerDist; + + /// Type of used real distribution + template + using real_dist_t = RealDist; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min( ) { + return Engine::min( ); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max( ) { + return Engine::max( ); + } + + /// Advances the internal state by z times + void discard( const unsigned long long z ) { + m_engine.discard( z ); + } + + /// Reseed by Seeder + void reseed( ) { + Seeder seeder; + seed( seeder( ) ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + void seed( const typename Engine::result_type value = + Engine::default_seed ) { + m_engine.seed( value ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template + void seed( Sseq& seq ) { + m_engine.seed( seq ); + } + + /// return random number from engine in [min(), max()] range + typename Engine::result_type get( ) { + return m_engine( ); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + bool is_equal( const Engine& other ) { + return m_engine == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template + void serialize( std::basic_ostream& ost ) { + ost << m_engine; + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template + void deserialize( std::basic_istream& ist ) { + ist >> m_engine; + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return IntegerDist{ from, to }( m_engine ); + return IntegerDist{ to, from }( m_engine ); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return RealDist{ from, to }( m_engine ); + return RealDist{ to, from }( m_engine ); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional::value, + short, unsigned short>::type; + + return static_cast( get( from, to ) ); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template< + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type + > + typename std::enable_if< + std::is_same::value + && details::is_supported_number::value + && details::is_supported_number::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed::value != std::is_unsigned::value + , C>::type get( A from = std::numeric_limits::min( ), + B to = std::numeric_limits::max( ) ) { + return get( static_cast( from ), static_cast( to ) ); + } + + /** + * \brief Generate a random character in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random character in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value + , T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) { + if (from < to) // Allow range from higher to lower + return static_cast(IntegerDist{ static_cast(from), static_cast(to) }(m_engine)); + return static_cast(IntegerDist{ static_cast(to), static_cast(from) }(m_engine)); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template + typename std::enable_if::value + , bool>::type get( const double probability = 0.5 ) { + assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range + return BoolDist{ probability }( m_engine ); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template + T get( std::initializer_list init_list ) { + assert( 0u != init_list.size( ) ); + return *get( init_list.begin( ), init_list.end( ) ); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template + typename std::enable_if::value + , InputIt>::type get( InputIt first, InputIt last ) { + const auto size = std::distance( first, last ); + if( 0 == size ) return last; + using diff_t = typename std::iterator_traits::difference_type; + return std::next( first, get( 0, size - 1 ) ); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template + auto get( Container& container ) -> + typename std::enable_if::value + , decltype(std::begin(container)) + >::type { + return get( std::begin( container ), std::end( container ) ); + } + + /** + * \brief Return random pointer from built-in array + * \param array The built-in array with elements + * \return Pointer to random element in array + */ + template + T* get( T( &array )[ N ] ) { + return std::addressof( array[ get( 0, N - 1 ) ] ); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template + typename Dist::result_type get( Args&&... args ) { + return Dist{ std::forward( args )... }( m_engine ); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template + typename Dist::result_type get( Dist& dist ) { + return dist( m_engine ); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template + void shuffle( RandomIt first, RandomIt last ) { + std::shuffle( first, last, m_engine ); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template + void shuffle( Container& container ) { + shuffle( std::begin( container ), std::end( container ) ); + } + + /// return internal engine by copy + Engine get_engine( ) const { + return m_engine; + } + + /// return internal engine by ref + Engine& engine() { + return m_engine; + } + protected: + /// return engine seeded by Seeder + static Engine make_seeded_engine( ) { + // Make seeder instance for seed return by reference like std::seed_seq + return Engine{ Seeder{ }( ) }; + } + protected: + /// The random number engine + Engine m_engine{ make_seeded_engine( ) }; + }; + + /** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses static methods API and data with static storage + * \note Not thread safe but more prefomance + */ + using random_static = basic_random_static; + + /** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses static methods API and data with thread_local storage + * \note Thread safe but less perfomance + */ + using random_thread_local = basic_random_thread_local; + + /** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses non static methods API and data with auto storage + * \note Not thread safe. Should construct on the stack at local scope + */ + using random_local = basic_random_local; + +} // namespace effolkronium + +#endif // #ifndef EFFOLKRONIUM_RANDOM_HPP diff --git a/source/common/world/Chunk.hpp b/source/common/world/Chunk.hpp index b8709c32c..07750f8c1 100644 --- a/source/common/world/Chunk.hpp +++ b/source/common/world/Chunk.hpp @@ -27,6 +27,7 @@ #ifndef CHUNK_HPP_ #define CHUNK_HPP_ +#include #include #include #include @@ -100,6 +101,8 @@ class Chunk : public gk::NonCopyable { using DataArray = u32[Chunk::height][Chunk::depth][Chunk::width]; const DataArray &data() const { return m_data; } + u32 data(int x, int y, int z) const { return m_data[z][y][x]; } + protected: s32 m_x; s32 m_y; @@ -113,9 +116,9 @@ class Chunk : public gk::NonCopyable { Chunk *m_surroundingChunks[6]{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; - bool m_hasChanged = false; - bool m_hasLightChanged = false; - bool m_isInitialized = false; + std::atomic_bool m_hasChanged{false}; + std::atomic_bool m_hasLightChanged{false}; + std::atomic_bool m_isInitialized{false}; std::unordered_map m_tickingBlocks; diff --git a/source/server/world/ServerChunk.hpp b/source/server/world/ServerChunk.hpp index a57fa0d6a..6c0a2ac6f 100644 --- a/source/server/world/ServerChunk.hpp +++ b/source/server/world/ServerChunk.hpp @@ -27,6 +27,8 @@ #ifndef SERVERCHUNK_HPP_ #define SERVERCHUNK_HPP_ +#include + #include #include "Chunk.hpp" @@ -47,7 +49,7 @@ class ServerChunk : public Chunk { void setSent(bool isSent) { m_isSent = isSent; } private: - bool m_isSent = false; + std::atomic_bool m_isSent{false}; }; #endif // SERVERCHUNK_HPP_ diff --git a/source/server/world/ServerWorld.cpp b/source/server/world/ServerWorld.cpp index 1de54d490..da5d9e5af 100644 --- a/source/server/world/ServerWorld.cpp +++ b/source/server/world/ServerWorld.cpp @@ -105,7 +105,7 @@ void ServerWorld::sendChunkData(const ClientInfo &client, ServerChunk &chunk) { for (u16 z = 0 ; z < CHUNK_HEIGHT ; ++z) { for (u16 y = 0 ; y < CHUNK_DEPTH ; ++y) { for (u16 x = 0 ; x < CHUNK_WIDTH ; ++x) { - packet << chunk.data()[z][y][x]; + packet << chunk.data(x, y, z); packet << chunk.lightmap().getLightData(x, y, z); BlockData *blockData = chunk.getBlockData(x, y, z); diff --git a/source/server/world/TerrainGenerator.cpp b/source/server/world/TerrainGenerator.cpp index 9811d1e2e..f4c3286af 100644 --- a/source/server/world/TerrainGenerator.cpp +++ b/source/server/world/TerrainGenerator.cpp @@ -43,7 +43,9 @@ void TerrainGenerator::fastNoiseGeneration(ServerChunk &chunk) const { noise.SetFrequency(1 / 256.0f); noise.SetFractalOctaves(4); - srand(chunk.x() + chunk.y() * CHUNK_WIDTH + chunk.z() * CHUNK_WIDTH * CHUNK_HEIGHT + 1337); + Random_t rand; + rand.seed(chunk.x() + chunk.y() * CHUNK_WIDTH + chunk.z() * CHUNK_WIDTH * CHUNK_HEIGHT + 1337); + Chunk *topChunk = chunk.getSurroundingChunk(Chunk::Top); for(int y = 0 ; y < CHUNK_DEPTH ; y++) { for(int x = 0 ; x < CHUNK_WIDTH ; x++) { @@ -71,13 +73,13 @@ void TerrainGenerator::fastNoiseGeneration(ServerChunk &chunk) const { // Try to place a tree if (chunk.getBlock(x, y, z - 1) == biome.getTopBlockID()) { for (const PlacementEntry::Tree &treePlacement : biome.getTrees()) { - if (rand() > RAND_MAX * treePlacement.probability) + if (!rand.get(treePlacement.probability)) continue; const Tree &tree = Registry::getInstance().getTree(treePlacement.treeID); // Trunk - int h = (rand() & (tree.trunkMaxHeight() - tree.trunkMinHeight())) + tree.trunkMinHeight(); + int h = rand.get(tree.trunkMinHeight(), tree.trunkMaxHeight()); for (int i = 0; i < h; i++) { chunk.setBlockRaw(x, y, z + i, tree.getLogBlockID()); } @@ -87,7 +89,7 @@ void TerrainGenerator::fastNoiseGeneration(ServerChunk &chunk) const { for (int iz = -3; iz <= 3; iz++) { for (int iy = -3; iy <= 3; iy++) { for (int ix = -3; ix <= 3; ix++) { - if (ix * ix + iy * iy + iz * iz < 8 + (rand() & 1) && !chunk.getBlock(x + ix, y + iy, z + h + iz)) { + if (ix * ix + iy * iy + iz * iz < 8 + rand.get(0, 1) && !chunk.getBlock(x + ix, y + iy, z + h + iz)) { chunk.setBlockRaw(x + ix, y + iy, z + h + iz, tree.getLeavesBlockID()); // FIXME: This is a temporary fix for the second part of #41 @@ -110,7 +112,7 @@ void TerrainGenerator::fastNoiseGeneration(ServerChunk &chunk) const { if (chunk.getBlock(x, y, z - 1) != flora.spawnsOnBlockID) continue; - if (rand() > RAND_MAX * flora.probability) + if (!rand.get(flora.probability)) continue; chunk.setBlockRaw(x, y, z, flora.blockID); @@ -122,7 +124,7 @@ void TerrainGenerator::fastNoiseGeneration(ServerChunk &chunk) const { // This code should be replaced by a proper "feature" implementation // which will also allow making stuff like villages easier bool placedPortal = false; - if (chunk.getBlock(x, y, z - 1) == biome.getTopBlockID() && rand() < RAND_MAX * 0.0002) { + if (chunk.getBlock(x, y, z - 1) == biome.getTopBlockID() && rand.get(0.0002)) { for (int ix = 0 ; ix < 4 ; ++ix) { for (int iz = 0 ; iz < 5 ; ++iz) { if (ix == 0 || iz == 0 || ix == 3 || iz == 4) @@ -157,10 +159,10 @@ void TerrainGenerator::fastNoiseGeneration(ServerChunk &chunk) const { // This could be achieved either by setting up a generation pipeline with stages, // processing neighboring chunks' ores every time, or generating them with noise. for (const PlacementEntry::Ore &ore : biome.getOres()) { - if (rand() > RAND_MAX * ore.probability) + if (!rand.get(ore.probability)) continue; - oreFloodFill(chunk, x, y, z, biome.getDeepBlockID(), ore.blockID, 2); + oreFloodFill(chunk, x, y, z, biome.getDeepBlockID(), ore.blockID, 2, rand); break; } @@ -187,35 +189,35 @@ void TerrainGenerator::fastNoiseGeneration(ServerChunk &chunk) const { } } -void TerrainGenerator::oreFloodFill(ServerChunk &chunk, double x, double y, double z, u16 toReplace, u16 replaceWith, int depth) const { +void TerrainGenerator::oreFloodFill(ServerChunk &chunk, double x, double y, double z, u16 toReplace, u16 replaceWith, int depth, Random_t &rand) const { if (depth < 0) return; if (chunk.getBlock(x, y, z) == replaceWith) return; if (chunk.getBlock(x, y, z) == toReplace) chunk.setBlockRaw(x, y, z, replaceWith); - oreFloodFill(chunk, x + 1, y, z, toReplace, replaceWith, depth - 1); - oreFloodFill(chunk, x - 1, y, z, toReplace, replaceWith, depth - 1); - oreFloodFill(chunk, x, y + 1, z, toReplace, replaceWith, depth - 1); - oreFloodFill(chunk, x, y - 1, z, toReplace, replaceWith, depth - 1); - oreFloodFill(chunk, x, y, z + 1, toReplace, replaceWith, depth - 1); - oreFloodFill(chunk, x, y, z - 1, toReplace, replaceWith, depth - 1); - - if (rand() % 15 == 0) - oreFloodFill(chunk, x + 1, y + 1, z + 1, toReplace, replaceWith, depth - 1); - if (rand() % 15 == 0) - oreFloodFill(chunk, x + 1, y + 1, z - 1, toReplace, replaceWith, depth - 1); - if (rand() % 15 == 0) - oreFloodFill(chunk, x + 1, y - 1, z + 1, toReplace, replaceWith, depth - 1); - if (rand() % 15 == 0) - oreFloodFill(chunk, x + 1, y - 1, z - 1, toReplace, replaceWith, depth - 1); - if (rand() % 15 == 0) - oreFloodFill(chunk, x - 1, y + 1, z + 1, toReplace, replaceWith, depth - 1); - if (rand() % 15 == 0) - oreFloodFill(chunk, x - 1, y + 1, z - 1, toReplace, replaceWith, depth - 1); - if (rand() % 15 == 0) - oreFloodFill(chunk, x - 1, y - 1, z + 1, toReplace, replaceWith, depth - 1); - if (rand() % 15 == 0) - oreFloodFill(chunk, x - 1, y - 1, z - 1, toReplace, replaceWith, depth - 1); + oreFloodFill(chunk, x + 1, y, z, toReplace, replaceWith, depth - 1, rand); + oreFloodFill(chunk, x - 1, y, z, toReplace, replaceWith, depth - 1, rand); + oreFloodFill(chunk, x, y + 1, z, toReplace, replaceWith, depth - 1, rand); + oreFloodFill(chunk, x, y - 1, z, toReplace, replaceWith, depth - 1, rand); + oreFloodFill(chunk, x, y, z + 1, toReplace, replaceWith, depth - 1, rand); + oreFloodFill(chunk, x, y, z - 1, toReplace, replaceWith, depth - 1, rand); + + if (rand.get(1.f / 15.f)) + oreFloodFill(chunk, x + 1, y + 1, z + 1, toReplace, replaceWith, depth - 1, rand); + if (rand.get(1.f / 15.f)) + oreFloodFill(chunk, x + 1, y + 1, z - 1, toReplace, replaceWith, depth - 1, rand); + if (rand.get(1.f / 15.f)) + oreFloodFill(chunk, x + 1, y - 1, z + 1, toReplace, replaceWith, depth - 1, rand); + if (rand.get(1.f / 15.f)) + oreFloodFill(chunk, x + 1, y - 1, z - 1, toReplace, replaceWith, depth - 1, rand); + if (rand.get(1.f / 15.f)) + oreFloodFill(chunk, x - 1, y + 1, z + 1, toReplace, replaceWith, depth - 1, rand); + if (rand.get(1.f / 15.f)) + oreFloodFill(chunk, x - 1, y + 1, z - 1, toReplace, replaceWith, depth - 1, rand); + if (rand.get(1.f / 15.f)) + oreFloodFill(chunk, x - 1, y - 1, z + 1, toReplace, replaceWith, depth - 1, rand); + if (rand.get(1.f / 15.f)) + oreFloodFill(chunk, x - 1, y - 1, z - 1, toReplace, replaceWith, depth - 1, rand); } float TerrainGenerator::noise2d(double x, double y, int octaves, float persistence) { diff --git a/source/server/world/TerrainGenerator.hpp b/source/server/world/TerrainGenerator.hpp index 465e40170..821e66d73 100644 --- a/source/server/world/TerrainGenerator.hpp +++ b/source/server/world/TerrainGenerator.hpp @@ -29,10 +29,14 @@ #include +#include + #include #include "TerrainBiomeSampler.hpp" +using Random_t = effolkronium::random_local; + class Dimension; class ServerChunk; @@ -45,7 +49,7 @@ class TerrainGenerator { private: void fastNoiseGeneration(ServerChunk &chunk) const; - void oreFloodFill(ServerChunk &chunk, double x, double y, double z, u16 toReplace, u16 replaceWith, int depth) const; + void oreFloodFill(ServerChunk &chunk, double x, double y, double z, u16 toReplace, u16 replaceWith, int depth, Random_t &rand) const; static float noise2d(double x, double y, int octaves, float persistence); static float noise3d_abs(double x, double y, double z, int octaves, float persistence);