diff --git a/CMakeLists.txt b/CMakeLists.txt index 526d68099..55d193624 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,6 +131,47 @@ OPTION(EQEMU_BUILD_TESTS "Build utility tests." OFF) OPTION(EQEMU_BUILD_CLIENT_FILES "Build Client Import/Export Data Programs." ON) OPTION(EQEMU_PREFER_LUA "Build with normal Lua even if LuaJIT is found." OFF) +#PRNG options +OPTION(EQEMU_ADDITIVE_LFIB_PRNG "Use Additive LFib for PRNG." OFF) +MARK_AS_ADVANCED(EQEMU_ADDITIVE_LFIB_PRNG) +OPTION(EQEMU_BIASED_INT_DIST "Use biased int dist instead of uniform." OFF) +MARK_AS_ADVANCED(EQEMU_BIASED_INT_DIST) +SET(EQEMU_CUSTOM_PRNG_ENGINE "" CACHE STRING "Custom random engine. (ex. std::default_random_engine)") +MARK_AS_ADVANCED(EQEMU_CUSTOM_PRNG_ENGINE) + +IF(CMAKE_COMPILER_IS_GNUCXX) + OPTION(EQEMU_SFMT19937 "Use GCC's extention for SIMD Fast MT19937." OFF) + MARK_AS_ADVANCED(EQEMU_SFMT19937) +ENDIF() + +IF(EQEMU_ADDITIVE_LFIB_PRNG) + ADD_DEFINITIONS(-DUSE_ADDITIVE_LFIB_PRNG) + IF(EQEMU_SFMT19937) + MESSAGE(STATUS "SFMT19937 and ADDITITVE_LFIB_PRNG both set, SFMT19937 ignored.") + SET(EQEMU_SFMT19937 OFF) + ENDIF() + IF(NOT EQEMU_CUSTOM_PRNG_ENGINE STREQUAL "") + MESSAGE(STATUS "CUSTOM_PRNG_ENGINE and ADDITITVE_LFIB_PRNG both set, CUSTOM_PRNG_ENGINE ignored.") + SET(EQEMU_CUSTOM_PRNG_ENGINE "") + ENDIF() +ENDIF() + +IF(EQEMU_SFMT19937) + ADD_DEFINITIONS(-DUSE_SFMT19937) + IF(NOT EQEMU_CUSTOM_PRNG_ENGINE STREQUAL "") + MESSAGE(STATUS "CUSTOM_PRNG_ENGINE and SFMT19937 both set, CUSTOM_PRNG_ENGINE ignored.") + SET(EQEMU_CUSTOM_PRNG_ENGINE "") + ENDIF() +ENDIF() + +IF(NOT EQEMU_CUSTOM_PRNG_ENGINE STREQUAL "") + ADD_DEFINITIONS(-DUSE_CUSTOM_PRNG_ENGINE=${EQEMU_CUSTOM_PRNG_ENGINE}) +ENDIF() + +IF(EQEMU_BIASED_INT_DIST) + ADD_DEFINITIONS(-DBIASED_INT_DIST) +ENDIF() + IF(EQEMU_COMMANDS_LOGGING) ADD_DEFINITIONS(-DCOMMANDS_LOGGING) ENDIF(EQEMU_COMMANDS_LOGGING) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 084ac9c4f..47edd22e8 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -451,6 +451,7 @@ SET(repositories ) SET(common_headers + additive_lagged_fibonacci_engine.h any.h base_packet.h base_data.h diff --git a/common/additive_lagged_fibonacci_engine.h b/common/additive_lagged_fibonacci_engine.h new file mode 100644 index 000000000..b396b4436 --- /dev/null +++ b/common/additive_lagged_fibonacci_engine.h @@ -0,0 +1,147 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2021 EQEMu Development Team (http://eqemulator.net) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#pragma once + +#include +#include +#include +#include +#include + +/* + * This is an additive lagged fibonacci generator as seen in The Art of Computer Programming, Vol. 2 + * This should roughly match the implementation that EQ's client uses and be compatible with our Random class + * + * EQ's rand looks like it was from an example implementation that as posted on pscode.com + * + * You might also want to consider defining BIASED_INT_DIST as well to more closely match EQ + */ + +namespace EQ { + template + class additive_lagged_fibonacci_engine { + static_assert(std::is_unsigned::value, "result_type must be an unsigned integral type"); + static_assert(0u < j && j < k, "0 < j < k"); + static_assert(0u < w && w <= std::numeric_limits::digits, + "template argument substituting w out of bounds"); + + public: + using result_type = UIntType; + static constexpr size_t word_size = w; + static constexpr size_t short_lag = j; + static constexpr size_t long_lag = k; + static constexpr result_type default_seed = 19780503u; // default for subtract_with_carry_engine + + additive_lagged_fibonacci_engine() : additive_lagged_fibonacci_engine(default_seed) {} + + explicit additive_lagged_fibonacci_engine(result_type sd) { seed(sd); } + + void seed(result_type seed = default_seed) + { + state1 = long_lag - long_lag; + state2 = long_lag - short_lag; + state[0] = static_cast(seed) & ((1u << word_size) - 1); + state[1] = 1; + for (int i = 2; i < long_lag; ++i) + state[i] = (state[i - 1] + state[i - 2]) & ((1u << word_size) - 1); + return; + } + // TODO: seed via seed_seq + + static constexpr result_type min() { return 0; } + static constexpr result_type max() { return ((1u << word_size) - 1) >> 6; } + + void discard(unsigned long long z) { + for (; z != 0ULL; --z) + (*this)(); + } + + result_type operator()() { + result_type rand = (state[state1] + state[state2]) & ((1u << word_size) - 1); + state[state1] = rand; + if (++state1 == long_lag) + state1 = 0; + if (++state2 == long_lag) + state2 = 0; + + return rand >> 6; + } + + private: + result_type state1; + result_type state2; + result_type state[long_lag]; + + public: + template + friend bool operator==(const additive_lagged_fibonacci_engine &x, + const additive_lagged_fibonacci_engine &y) + { + return std::equal(x.state, x.state + long_lag, y.state) && x.state1 == y.state1 && + x.state2 == y.state2; + } + + template + friend bool operator!=(const additive_lagged_fibonacci_engine &x, + const additive_lagged_fibonacci_engine &y) + { return !(x == y); } + + template + friend std::basic_ostream & + operator<<(std::basic_istream &os, additive_lagged_fibonacci_engine &x) + { + using ios_base = typename std::basic_istream::ios_base; + + const typename ios_base::fmtflags flags = os.flags(); + const CharT fill = os.fill(); + const CharT space = os.widen(' '); + os.flags(ios_base::dec | ios_base::fixed | ios_base::left); + os.fill(space); + + for (size_t i = 0; i < long_lag; ++i) + os << x.state[i] << space; + os << x.state1 << space << x.state2; + + os.flags(flags); + os.fill(fill); + return os; + } + + template + friend std::basic_istream & + operator>>(std::basic_istream &is, additive_lagged_fibonacci_engine &x) + { + using ios_base = typename std::basic_istream::ios_base; + + const typename ios_base::fmtflags flags = is.flags(); + is.flags(ios_base::dec | ios_base::skipws); + + for (size_t i = 0; i < long_lag; ++i) + is >> x.state[i]; + is >> x.state1; + is >> x.state2; + + is.flags(flags); + return is; + } + }; + + using EQRand = additive_lagged_fibonacci_engine; +}; + diff --git a/common/random.h b/common/random.h index 203755efd..6d351121f 100644 --- a/common/random.h +++ b/common/random.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2014 EQEMu Development Team (http://eqemulator.net) + Copyright (C) 2001-2021 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,10 +25,24 @@ #include #include -/* This uses mt19937 seeded with the std::random_device - * The idea is to have this be included as a member of another class - * so mocking out for testing is easier - * If you need to reseed random.Reseed() +#ifdef USE_SFMT19937 +// only GCC supports, so lets turn it off +#ifndef __GNUC__ +#undef USE_SFMT19937 +#endif +#ifdef __clang__ +#undef USE_SFMT19937 +#endif +#endif + +#ifdef USE_ADDITIVE_LFIB_PRNG +#include "additive_lagged_fibonacci_engine.h" +#elif defined(USE_SFMT19937) +#include +#endif + +/* This uses mt19937 (or other compatible engine) seeded with the std::random_device. The idea is to have this be + * included as a member of another class so mocking out for testing is easier If you need to reseed random.Reseed() * Eventually this should be derived from an abstract base class */ @@ -40,7 +54,12 @@ namespace EQ { { if (low > high) std::swap(low, high); +// EQ uses biased int distribution, so I guess we can support it :P +#ifdef BIASED_INT_DIST + return low + m_gen() % (high - low + 1); +#else return int_dist(m_gen, int_param_t(low, high)); // [low, high] +#endif } // AKA old MakeRandomFloat @@ -98,11 +117,24 @@ namespace EQ { } private: +#ifndef BIASED_INT_DIST typedef std::uniform_int_distribution::param_type int_param_t; - typedef std::uniform_real_distribution::param_type real_param_t; - std::mt19937 m_gen; std::uniform_int_distribution int_dist; +#endif + typedef std::uniform_real_distribution::param_type real_param_t; std::uniform_real_distribution real_dist; +// define USE_CUSTOM_PRNG_ENGINE to any supported random engine like: +// #define USE_CUSTOM_PRNG_ENGINE std::default_random_engine +#ifdef USE_ADDITIVE_LFIB_PRNG + EQRand +#elif defined(USE_SFMT19937) + __gnu_cxx::sfmt19937 +#elif defined(USE_CUSTOM_PRNG_ENGINE) + USE_CUSTOM_PRNG_ENGINE +#else + std::mt19937 +#endif + m_gen; }; }