diff --git a/plugins/cxxrandom.cpp b/plugins/cxxrandom.cpp index 23b10931e..517d460ea 100644 --- a/plugins/cxxrandom.cpp +++ b/plugins/cxxrandom.cpp @@ -20,22 +20,33 @@ Updated: Dec. 21 2017 #include #include #include +#include +#include +#include +#include "Error.h" #include "Core.h" #include "DataFuncs.h" #include #include #include - +/* +typedef unsigned short uint16_t; +typedef unsigned long long uint64_t; +typedef long long int64_t; +using uint16_t = unsigned short; +using uint64_t = unsigned long long; +using int64_t = long long;*/ using namespace DFHack; DFHACK_PLUGIN("cxxrandom"); -#define PLUGIN_VERSION 1.0 - +#define PLUGIN_VERSION 2.0 +color_ostream *cout = nullptr; //command_result cxxrandom (color_ostream &out, std::vector & parameters); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { + cout = &out; /* commands.push_back(PluginCommand( "cxxrandom", "C++xx Random Numbers", cxxrandom, false, @@ -64,127 +75,237 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan return CR_OK; } +#pragma region "EnginesKeeper Stuff" + +class EnginesKeeper +{ +private: + EnginesKeeper() {} + std::unordered_map m_engines; + uint16_t counter = 0; +public: + static EnginesKeeper& Instance() + { + static EnginesKeeper instance; + return instance; + } + uint16_t NewEngine( uint64_t seed ) + { + std::mt19937_64 engine( seed != 0 ? seed : std::chrono::system_clock::now().time_since_epoch().count() ); + m_engines[++counter] = engine; + return counter; + } + void DestroyEngine( uint16_t id ) + { + m_engines.erase( id ); + } + void NewSeed( uint16_t id, uint64_t seed ) + { + CHECK_INVALID_ARGUMENT( m_engines.find( id ) != m_engines.end() ); + m_engines[id].seed( seed != 0 ? seed : std::chrono::system_clock::now().time_since_epoch().count() ); + } + std::mt19937_64& RNG( uint16_t id ) + { + CHECK_INVALID_ARGUMENT( m_engines.find( id ) != m_engines.end() ); + return m_engines[id]; + } +}; + +#pragma endregion + +#pragma region "EngineKeeper Wrappers" + +uint16_t GenerateEngine( uint64_t seed ) +{ + return EnginesKeeper::Instance().NewEngine( seed ); +} + +void DestroyEngine( uint16_t id ) +{ + EnginesKeeper::Instance().DestroyEngine( id ); +} -std::default_random_engine& RNG() +void NewSeed( uint16_t id, uint64_t seed ) { - static std::default_random_engine instance(std::chrono::system_clock::now().time_since_epoch().count()); - return instance; + EnginesKeeper::Instance().NewSeed( id, seed ); } -void seedRNG(unsigned short seed) +#pragma endregion + +#pragma region "std Distribution Rollers" + +int rollInt(uint16_t id, int min, int max) +{ + std::uniform_int_distribution ND(min, max); + return ND(EnginesKeeper::Instance().RNG(id)); +} + +double rollDouble(uint16_t id, double min, double max) { - RNG() = std::default_random_engine(seed); + std::uniform_real_distribution ND(min, max); + return ND(EnginesKeeper::Instance().RNG(id)); +} + +double rollNormal(uint16_t id, double mean, double stddev) +{ + std::normal_distribution ND(mean, stddev); + return ND(EnginesKeeper::Instance().RNG(id)); +} + +bool rollBool(uint16_t id, float p) +{ + std::bernoulli_distribution ND(p); + return ND(EnginesKeeper::Instance().RNG(id)); } +#pragma endregion +#pragma region "Number Sequence Stuff" -class SimpleNumDistribution +class NumberSequence { private: unsigned short m_position = 0; - std::vector m_distribution; - + std::vector m_numbers; public: - SimpleNumDistribution(unsigned short N) + NumberSequence(){} + NumberSequence( int64_t start, int64_t end ) { - m_position = 0; - m_distribution.reserve(N); - for(int i = 1; i <= N; ++i) + for( int64_t i = start; i <= end; ++i ) { - m_distribution.push_back(i); + m_numbers.push_back( i ); } - Reset(); } - - void Reset() + void Add( int64_t num ) { m_numbers.push_back( num ); } + void Reset() { m_numbers.clear(); } + int64_t Next() { - std::shuffle(std::begin(m_distribution), std::end(m_distribution), RNG()); + if(m_position >= m_numbers.size()) + { + m_position = 0; + } + return m_numbers[m_position++]; } - - unsigned short Length() const { return m_distribution.size(); } - - unsigned short Next() + void Shuffle( uint16_t id ) + { + std::shuffle( std::begin( m_numbers ), std::end( m_numbers ), EnginesKeeper::Instance().RNG( id ) ); + } + void Print() { - if(m_position >= m_distribution.size()) + char buffer1[256] = {0}; + char buffer2[256] = {0}; + for( auto v : m_numbers ) { - m_position = 0; - Reset(); + sprintf( buffer2, "%s%d", buffer1, v ); + sprintf( buffer1, "%s ", buffer2 ); } - return m_distribution[m_position++]; + cout->print( buffer1 ); } }; +class SequenceKeeper +{ +private: + SequenceKeeper() {} + std::unordered_map m_sequences; + uint16_t counter = 0; +public: + static SequenceKeeper& Instance() + { + static SequenceKeeper instance; + return instance; + } + uint16_t MakeNumSequence( int64_t start, int64_t end ) + { + m_sequences[++counter] = NumberSequence( start, end ); + return counter; + } + uint16_t MakeNumSequence() + { + m_sequences[++counter] = NumberSequence(); + return counter; + } + void DestroySequence( uint16_t id ) + { + m_sequences.erase( id ); + } + void AddToSequence( uint16_t id, int64_t num ) + { + CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); + m_sequences[id].Add( num ); + } + void Shuffle( uint16_t id, uint16_t rng_id ) + { + CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); + m_sequences[id].Shuffle( rng_id ); + } + int64_t NextInSequence( uint16_t id ) + { + CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); + return m_sequences[id].Next(); + } + void PrintSequence( uint16_t id ) + { + CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); + auto seq = m_sequences[id]; + seq.Print(); + } +}; +#pragma endregion -typedef std::unordered_map DistributionContainer; -DistributionContainer& GetDistribContainer() -{ - static DistributionContainer instance; - return instance; -} +#pragma region "Sequence Wrappers" -void resetIndexRolls(std::string ref, unsigned short N) +uint16_t MakeNumSequence( int64_t start, int64_t end ) { - DistributionContainer& ND_index = GetDistribContainer(); - auto iter = ND_index.find(ref); - if(iter == ND_index.end() || iter->second.Length() != N ) + if( start == end ) { - if(iter != ND_index.end()) - ND_index.erase(iter); - - iter = ND_index.emplace(ref, SimpleNumDistribution(N)).first; + return SequenceKeeper::Instance().MakeNumSequence(); } - iter->second.Reset(); + return SequenceKeeper::Instance().MakeNumSequence( start, end ); } -int rollIndex(std::string ref, unsigned short N) +void DestroyNumSequence( uint16_t id ) { - DistributionContainer& ND_index = GetDistribContainer(); - auto iter = GetDistribContainer().find(ref); - if(iter == ND_index.end() || iter->second.Length() != N ) - { - if(iter != ND_index.end()) - ND_index.erase(iter); - - iter = ND_index.emplace(ref, SimpleNumDistribution(N)).first; - } - return iter->second.Next(); + SequenceKeeper::Instance().DestroySequence( id ); } - -int rollInt(int min, int max) +void AddToSequence( uint16_t id, int64_t num ) { - std::uniform_int_distribution ND(min, max); - return ND(RNG()); + SequenceKeeper::Instance().AddToSequence( id, num ); } - -double rollDouble(double min, double max) + +void ShuffleSequence( uint16_t rngID, uint16_t id ) { - std::uniform_real_distribution ND(min, max); - return ND(RNG()); + SequenceKeeper::Instance().Shuffle( id, rngID ); } - -double rollNormal(double mean, double stddev) + +int64_t NextInSequence( uint16_t id ) { - std::normal_distribution ND(mean, stddev); - return ND(RNG()); + return SequenceKeeper::Instance().NextInSequence( id ); } - -bool rollBool(float p) + +void DebugSequence( uint16_t id ) { - std::bernoulli_distribution ND(p); - return ND(RNG()); + SequenceKeeper::Instance().PrintSequence( id ); } +#pragma endregion DFHACK_PLUGIN_LUA_FUNCTIONS { - DFHACK_LUA_FUNCTION(resetIndexRolls), - DFHACK_LUA_FUNCTION(rollIndex), - DFHACK_LUA_FUNCTION(seedRNG), + DFHACK_LUA_FUNCTION(GenerateEngine), + DFHACK_LUA_FUNCTION(DestroyEngine), + DFHACK_LUA_FUNCTION(NewSeed), DFHACK_LUA_FUNCTION(rollInt), DFHACK_LUA_FUNCTION(rollDouble), DFHACK_LUA_FUNCTION(rollNormal), DFHACK_LUA_FUNCTION(rollBool), + DFHACK_LUA_FUNCTION(MakeNumSequence), + DFHACK_LUA_FUNCTION(DestroyNumSequence), + DFHACK_LUA_FUNCTION(AddToSequence), + DFHACK_LUA_FUNCTION(ShuffleSequence), + DFHACK_LUA_FUNCTION(NextInSequence), + DFHACK_LUA_FUNCTION(DebugSequence), DFHACK_LUA_END }; diff --git a/plugins/lua/cxxrandom.lua b/plugins/lua/cxxrandom.lua index 7f38ce5ce..1521c4c9e 100644 --- a/plugins/lua/cxxrandom.lua +++ b/plugins/lua/cxxrandom.lua @@ -1,3 +1,214 @@ local _ENV = mkmodule('plugins.cxxrandom') +function MakeNewEngine(seed) + if type(seed) == 'number' then + if seed == 0 then + print(":WARNING: Seeds equal to 0 are used if no seed is provided. This indicates to cxxrandom.plug.dll that the engine needs to be seeded with the current time.\nRecommendation: use a non-zero value for your seed, or don't provide a seed to use the time since epoch(1969~).") + end + return GenerateEngine(seed) + elseif type(seed) == 'nil' then + return GenerateEngine(0) + else + error("Argument `seed` must be a number, or nil.") + end +end + +--Class: crng +------------- +crng = {} +function crng:new(engineID, destroyEngineOnDestruction, distrib) + local o = {} + self.__index = self + local idtype = type(engineID) + local flagtype = type(destroyEngineOnDestruction) + + if idtype == 'number' then + o.rngID = engineID + elseif idtype == 'nil' then + o.rngID = GenerateEngine(0) + else + error("Invalid argument type (engineID): " .. tostring(engineID)) + end + + if flagtype ~= 'nil' and flagtype == 'boolean' then + o.destroyid = destroyEngineOnDestruction + elseif flagtype == 'nil' then + o.destroyid = true + else + error("Invalid arugment type (destroyEngineOnDestruction): " .. tostring(destroyEngineOnDestruction)) + end + + if type(distrib) ~= 'nil' then + if type(distrib) == 'table' and type(distrib.next) == 'function' then + o.distrib = distrib + o.distrib.rngID = o.rngID + else + error("Invalid distribution used as an argument. Cannot set this as the number distribution.") + end + end + setmetatable(o,self) + return o +end +--crng destructor - we may need to destroy the engine, the user may be doing it manually though +function crng:__gc() + if self.destroyid then + DestroyEngine(self.rngID) + end +end +function crng:changeSeed(seed) + if type(seed) == 'number' then + if seed == 0 then + print(":WARNING: Seeds equal to 0 are used if no seed is provided. This indicates to cxxrandom.plug.dll that the engine needs to be seeded with the current time.\nRecommendation: use a non-zero value for your seed, or don't provide a seed to use the time since epoch(1969~).") + end + return NewSeed(self.rngID, seed) + elseif type(seed) == 'nil' then + return NewSeed(self.rngID, 0) + else + error("Argument `seed` must be a number, or nil.") + end +end +function crng:setNumDistrib(distrib) + if type(distrib) == 'table' and type(distrib.next) == 'function' then + self.distrib = distrib + self.distrib.rngID = self.rngID + else + error("Invalid distribution used as an argument. Cannot set this as the number distribution.") + end +end +function crng:next() + if type(self.distrib) == 'table' and type(self.distrib.next) == 'function' then + return self.distrib:next(self.rngID) + else + error("crng object does not have a valid number distribution set") + end +end +function crng:shuffle() + if type(self.distrib) == 'table' and type(self.distrib.shuffle) == 'function' then + self.distrib:shuffle(self.rngID) + else + print(":WARNING: No self.distrib.shuffle not found.") + changeSeed(0) + end +end + +--Class: normal_distribution +---------------------------- +normal_distribution = {} +function normal_distribution:new(avg, stddev) + local o = {} + self.__index = self + if type(avg) ~= 'number' or type(stddev) ~= 'number' then + error("Invalid arguments in normal_distribution construction. Average and standard deviation must be numbers.") + end + o.average = avg + o.std_deviation = stddev + setmetatable(o,self) + return o +end +function normal_distribution:next(id) + return rollNormal(id, self.average, self.std_deviation) +end + +--Class: real_distribution +---------------------------- +real_distribution = {} +function real_distribution:new(min, max) + local o = {} + self.__index = self + if type(min) ~= 'number' or type(max) ~= 'number' then + error("Invalid arguments in real_distribution construction. min and max must be numbers.") + end + o.min = min + o.max = max + setmetatable(o,self) + return o +end +function real_distribution:next(id) + return rollDouble(id, self.min, self.max) +end + +--Class: int_distribution +---------------------------- +int_distribution = {} +function int_distribution:new(min, max) + local o = {} + self.__index = self + if type(min) ~= 'number' or type(max) ~= 'number' then + error("Invalid arguments in int_distribution construction. min and max must be numbers.") + end + o.min = min + o.max = max + setmetatable(o,self) + return o +end +function int_distribution:next(id) + return rollInt(id, self.min, self.max) +end + +--Class: bool_distribution +---------------------------- +bool_distribution = {} +function bool_distribution:new(chance) + local o = {} + self.__index = self + if type(min) ~= 'number' or type(max) ~= 'number' then + error("Invalid arguments in bool_distribution construction. min and max must be numbers.") + end + o.p = chance + setmetatable(o,self) + return o +end +function bool_distribution:next(id) + return rollBool(id, self.p) +end + +--Class: num_sequence +---------------------------- +num_sequence = {} +function num_sequence:new(a,b) + local o = {} + self.__index = self + local btype = type(b) + local atype = type(a) + if atype == 'number' and btype == 'number' then + if a == b then + print(":WARNING: You've provided two equal arguments to initialize your sequence with. This is the mechanism used to indicate to cxxrandom.plug.dll that an empty sequence is desired.\nRecommendation: provide no arguments if you wish for an empty sequence.") + end + o.seqID = MakeNumSequence(a, b) + elseif atype == 'table' then + o.seqID = MakeNumSequence(0,0) + for _,v in pairs(a) do + if type(v) ~= 'number' then + error("num_sequence can only be initialized using numbers. " .. tostring(v) .. " is not a number.") + end + AddToSequence(o.seqID, v) + end + elseif atype == "nil" and btype == "nil" then + o.seqID = MakeNumSequence(0,0) + else + error("Invalid arguments - a: " .. tostring(a) .. " and b: " .. tostring(b)) + end + print("seqID:"..o.seqID) + setmetatable(o,self) + return o +end +function num_sequence:__gc() + DestroyNumSequence(self.seqID) +end +function num_sequence:add(x) + if type(x) ~= 'number' then + error("Cannot add non-number to num_sequence.") + end + AddToSequence(self.seqID, x) +end +function num_sequence:next() + return NextInSequence(self.seqID) +end +function num_sequence:shuffle() + if self.rngID == 'nil' then + error("Add num_sequence object to crng as distribution, before attempting to shuffle.") + end + ShuffleSequence(self.rngID, self.seqID) +end + return _ENV \ No newline at end of file