Merge remote-tracking branch 'cppcooper/cxxrandom-rel' into develop
commit
862fa08ba6
@ -0,0 +1,311 @@
|
||||
/* Plugin for exporting C++11 random number functionality
|
||||
*Exports functions for random number generation
|
||||
*Functions:
|
||||
- seedRNG(seed)
|
||||
- rollInt(min, max)
|
||||
- rollDouble(min, max)
|
||||
- rollNormal(mean, std_deviation)
|
||||
- rollBool(chance_for_true)
|
||||
- resetIndexRolls(string, array_length) --String identifies the instance of SimpleNumDistribution to reset
|
||||
- rollIndex(string, array_length) --String identifies the instance of SimpleNumDistribution to use
|
||||
--(Shuffles a vector of indices, Next() increments through then reshuffles when end() is reached)
|
||||
Author: Josh Cooper
|
||||
Created: Dec. 13 2017
|
||||
Updated: Dec. 21 2017
|
||||
*/
|
||||
|
||||
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "Error.h"
|
||||
#include "Core.h"
|
||||
#include "DataFuncs.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
/*
|
||||
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 2.0
|
||||
color_ostream *cout = nullptr;
|
||||
|
||||
//command_result cxxrandom (color_ostream &out, std::vector <std::string> & parameters);
|
||||
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
cout = &out;
|
||||
/*
|
||||
commands.push_back(PluginCommand(
|
||||
"cxxrandom", "C++xx Random Numbers", cxxrandom, false,
|
||||
" This plugin cannot be used on the commandline.\n"
|
||||
" Import into a lua script to have access to exported RNG functions:\n"
|
||||
" local rng = require('plugins.cxxrandom')\n\n"
|
||||
" Exported Functions:\n"
|
||||
" rng.ResetIndexRolls(string_ref, total_indices)\n"
|
||||
" rng.RollIndex(string_ref, total_indices)\n"
|
||||
" rng.RollInt(min, max)\n"
|
||||
" rng.RollDouble(min, max)\n"
|
||||
" rng.RollNormal(mean, std_deviation)\n"
|
||||
" rng.RollBool(chance_of_true)\n"
|
||||
" rng.BlastDistributions()\n\n"
|
||||
));*/
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown (color_ostream &out)
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
#pragma region "EnginesKeeper Stuff"
|
||||
|
||||
class EnginesKeeper
|
||||
{
|
||||
private:
|
||||
EnginesKeeper() {}
|
||||
std::unordered_map<uint16_t, std::mt19937_64> 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 );
|
||||
}
|
||||
|
||||
void NewSeed( uint16_t id, uint64_t seed )
|
||||
{
|
||||
EnginesKeeper::Instance().NewSeed( id, seed );
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region "std Distribution Rollers"
|
||||
|
||||
int rollInt(uint16_t id, int min, int max)
|
||||
{
|
||||
std::uniform_int_distribution<int> ND(min, max);
|
||||
return ND(EnginesKeeper::Instance().RNG(id));
|
||||
}
|
||||
|
||||
double rollDouble(uint16_t id, double min, double max)
|
||||
{
|
||||
std::uniform_real_distribution<double> ND(min, max);
|
||||
return ND(EnginesKeeper::Instance().RNG(id));
|
||||
}
|
||||
|
||||
double rollNormal(uint16_t id, double mean, double stddev)
|
||||
{
|
||||
std::normal_distribution<double> 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 NumberSequence
|
||||
{
|
||||
private:
|
||||
unsigned short m_position = 0;
|
||||
std::vector<int64_t> m_numbers;
|
||||
public:
|
||||
NumberSequence(){}
|
||||
NumberSequence( int64_t start, int64_t end )
|
||||
{
|
||||
for( int64_t i = start; i <= end; ++i )
|
||||
{
|
||||
m_numbers.push_back( i );
|
||||
}
|
||||
}
|
||||
void Add( int64_t num ) { m_numbers.push_back( num ); }
|
||||
void Reset() { m_numbers.clear(); }
|
||||
int64_t Next()
|
||||
{
|
||||
if(m_position >= m_numbers.size())
|
||||
{
|
||||
m_position = 0;
|
||||
}
|
||||
return m_numbers[m_position++];
|
||||
}
|
||||
void Shuffle( uint16_t id )
|
||||
{
|
||||
std::shuffle( std::begin( m_numbers ), std::end( m_numbers ), EnginesKeeper::Instance().RNG( id ) );
|
||||
}
|
||||
void Print()
|
||||
{
|
||||
char buffer1[256] = {0};
|
||||
char buffer2[256] = {0};
|
||||
for( auto v : m_numbers )
|
||||
{
|
||||
sprintf( buffer2, "%s%d", buffer1, v );
|
||||
sprintf( buffer1, "%s ", buffer2 );
|
||||
}
|
||||
cout->print( buffer1 );
|
||||
}
|
||||
};
|
||||
|
||||
class SequenceKeeper
|
||||
{
|
||||
private:
|
||||
SequenceKeeper() {}
|
||||
std::unordered_map<uint16_t, NumberSequence> 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
|
||||
|
||||
#pragma region "Sequence Wrappers"
|
||||
|
||||
uint16_t MakeNumSequence( int64_t start, int64_t end )
|
||||
{
|
||||
if( start == end )
|
||||
{
|
||||
return SequenceKeeper::Instance().MakeNumSequence();
|
||||
}
|
||||
return SequenceKeeper::Instance().MakeNumSequence( start, end );
|
||||
}
|
||||
|
||||
void DestroyNumSequence( uint16_t id )
|
||||
{
|
||||
SequenceKeeper::Instance().DestroySequence( id );
|
||||
}
|
||||
|
||||
void AddToSequence( uint16_t id, int64_t num )
|
||||
{
|
||||
SequenceKeeper::Instance().AddToSequence( id, num );
|
||||
}
|
||||
|
||||
void ShuffleSequence( uint16_t rngID, uint16_t id )
|
||||
{
|
||||
SequenceKeeper::Instance().Shuffle( id, rngID );
|
||||
}
|
||||
|
||||
int64_t NextInSequence( uint16_t id )
|
||||
{
|
||||
return SequenceKeeper::Instance().NextInSequence( id );
|
||||
}
|
||||
|
||||
void DebugSequence( uint16_t id )
|
||||
{
|
||||
SequenceKeeper::Instance().PrintSequence( id );
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
DFHACK_PLUGIN_LUA_FUNCTIONS {
|
||||
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
|
||||
};
|
||||
|
@ -0,0 +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
|
Loading…
Reference in New Issue