Compare commits

...

2 Commits

Author SHA1 Message Date
Avril 2458a2b969
Added skeleton for new RNG type: Lorenz Attractor.
1 year ago
Avril 295da7df02
Started branch "progress", for working on timed-out progress indicator. Current build succeeds but does not use progress indicators at all.
1 year ago

@ -7,9 +7,9 @@ INCLUDE = include
PROJECT=shuffle3
# Link to these libraries dynamicalls
SHARED_LIBS=
SHARED_LIBS=fmt
# Link to these libraries statically
STATIC_LIBS=fmt
STATIC_LIBS=
# Currently supported:
# _FS_SPILL_BUFFER: Use file backed buffer instead of memory backed one for unshuffling. See `shuffle3.h`.

@ -0,0 +1,36 @@
#ifndef _ERROR_H
#define _ERROR_H
#ifndef $PASTE
# define $_PASTE(x,y) x ## y
# define $PASTE(x,y) $_PASTE(x,y)
#endif
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
constexpr inline bool is_noexcept=
#if __cpp_exceptions
#define EXCEPT 1
//#define try try
//#define catch(...) catch(__VA_ARGS__)
false
#else
#define EXCEPT 0
#define NOEXCEPT
//#define catch(...) __try {} catch(__VA_ARGS__)
//#define try if constexpr(!is_noexcept)
//#define throw (void)0
true
#endif
;
#endif
#endif /* _ERROR_H */

@ -2,6 +2,10 @@
#include <string>
#include <memory>
#include <utility>
#include <array>
#include <utility>
#include <cstdio>
#ifndef __cplusplus
@ -10,28 +14,78 @@
namespace pr {
namespace details [[gnu::visibility("inernal")]]{
namespace details [[gnu::visibility("internal")]] {
/*
template<template<typename...> typename P>
struct generic_valid { template<typename... Args> constexpr static inline bool value = requires{ typename P<Args...>; }; };
template<template<typename...> class P>
struct generic : std::conditional_t<generic_valid<P>::value,
std::true_type,
std::false_type>
{ template<typename... Args> using type = P<Args...>; };
template<template<typename> typename T, typename... Args>
concept Generic = generic_valid<T>::template value<Args...>;
template<Generic T, typename... Args>
using generic_t = generic<T>::template type<Args...>;
template<template<typename> template T>
concept AnyGeneric = requires{ typename generic_valid<T>; };
*/
template<typename T>
T* take(T*& ptr) noexcept { return std::exchange(ptr, nullptr); }
T* take(T*&& ptr) noexcept { return std::exchange(ptr, nullptr); }
/*
template<template<typename U> Inst, typename T>
struct is_inst_of { constexpr static inline bool value = std::is_same_v<T, Inst<U>>; };
template<typename T, typename I>
constexpr inline bool is_inst_of_v = is_inst_of<I, T>::value;
*/
template<typename U, typename T>
constexpr std::unique_ptr<U> static_ucast(std::unique_ptr<T>&& from) noexcept
{ return std::unique_ptr<U>{static_cast<U*>(std::move(from).release())}; }
template<typename U, typename T>
constexpr std::shared_ptr<U> static_ucast(std::shared_ptr<T>&& from) noexcept
{ return std::static_pointer_cast<U>(std::move(from)); }
template<typename Ptr, typename From> requires(requires(From&& p) {
{ p.release() };
requires(std::is_constructible_v<Ptr, decltype(p.release())>);
})
constexpr Ptr static_pcast(From&& p) noexcept { return Ptr{p.release()}; }
}
using increment_t = long double;
template<typename T>
concept Bar = requires(T& v) {
concept Bar = requires(std::remove_const_t<std::remove_reference_t<T>>& v) {
{ v.spin(increment_t(0)) };
{ std::as_const(v).aux() } -> std::convertible_to<std::string_view>;
//{ v.aux() } -> std::assignable_from<std::string_view>;
{ (v.aux() = std::string{}) } -> std::same_as<std::add_lvalue_reference_t<decltype(v.aux())>>;
//requires(std::assignable_from<std::add_lvalue_reference_t<decltype(v.aux())>, std::string_view>);
{ v.aux() += std::string_view{} } -> std::convertible_to<decltype(v.aux())>;
{ v.aux(std::declval<std::string&&>()) } -> std::convertible_to<std::string_view>;
};
template<Bar>
struct Carrier;
struct Dynamic {
template<Bar B> friend class Carrier<B>;
template<Bar> friend class Carrier;
constexpr auto make(Bar auto&& bar) noexcept(std::is_nothrow_move_constructible_v<std::remove_reference_t<decltype(bar)>>);
constexpr virtual void spin(increment_t) =0;
constexpr virtual std::string& aux() =0;
constexpr virtual std::string const& aux() const noexcept =0;
constexpr virtual std::string aux(std::string&& s) { return std::exchange(aux(), std::move(s)); }
constexpr virtual ~Dynamic() {}
private:
@ -43,7 +97,7 @@ namespace pr {
template<typename... Args> requires(std::constructible_from<B, Args...>)
constexpr Carrier(Args&&... args) noexcept(std::is_nothrow_constructible_v<B, Args...>)
: Dynamic(), member_(std::forward<decltype(args)>(args)) {}
: Dynamic(), member_(std::forward<decltype(args)>(args)...) {}
constexpr Carrier(Carrier&& m) noexcept(std::is_nothrow_move_constructible_v<B>)
requires(std::is_move_constructible_v<B>)
: Dynamic(), member_(std::move(m.member_)) {}
@ -55,9 +109,14 @@ namespace pr {
{ if(this != &m) member_ = std::move(m.member_); return *this; }
constexpr Carrier& operator=(Carrier const& c) noexcept(std::is_nothrow_copy_assignable_v<B>) requires(std::is_copy_assignable_v<B>)
{ if(this != &m) member_ = c.member_; return *this; }
{ if(this != &c) member_ = c.member_; return *this; }
constexpr void spin(increment_t a) override { member_.spin(a); }
constexpr std::string& aux() override { return member_.aux(); }
constexpr std::string const& aux() const noexcept override { return member_.aux(); }
constexpr std::string aux(std::string&& s) override { return member_.aux(std::move(s)); }
template<typename... Args> requires(std::is_invocable_v<B::spin, B&, increment_t, Args...>)
constexpr void spin(increment_t a, Args&&... args) noexcept(std::is_nothrow_invocable_v<B::spin, B&, increment_t, decltype(args)...>)
{ return member_.spin(a, std::forward<decltype(args)>(args)...); }
@ -75,33 +134,77 @@ namespace pr {
constexpr auto Dynamic::make(Bar auto&& bar) noexcept(std::is_nothrow_move_constructible_v<std::remove_reference_t<decltype(bar)>>) { return Carrier(std::move(bar)); }
template<Bar T>
constexpr std::unique_ptr<Dynamic> make_dyn(T&& bar) noexcept(std::is_nothrow_move_constructible_v<T>)
template<Bar T, typename Ptr = std::unique_ptr<Dynamic>>
constexpr Ptr make_dyn(T&& bar) noexcept(std::is_nothrow_move_constructible_v<T>)
{
template<typename T, template<typename U> Inst>
constexpr bool is_inst_of = requires{
requires(std::is_same_v<T, Inst<U>>);
};
template<typename U, typename T, template<typename...> Ptr = std::unique_ptr>
constexpr Ptr<U> static_ucast(Ptr<T>&& from) noexcept
{ return Ptr<U>{static_cast<U*>(std::move(from).release())}; }
template<typename U, typename T>
constexpr std::shared_ptr<U> static_ucast<U, T, std::shared_ptr>(std::shared_ptr<T>&& from) noexcept
{ return std::static_pointer_cast<U>(std::move(from)); }
using namespace details;
if constexpr(requires{
typename T::held_type;
requires(std::is_same_v<Carrier<typename T::held_type>, T>);
}) return static_pcast<Ptr>(static_ucast<Dynamic>(std::make_unique<T>(std::move(bar))));
else return static_pcast<Ptr>(static_ucast<Dynamic>(std::make_unique<Carrier<T>>(std::move(bar))));
}
if constexpr(is_inst_of<T, Carrier>) return static_ucast<Dynamic>(std::make_unique<T>(std::move(bar)));
else return static_ucast<Dynamic>(std::make_unique<Carrier<T>>(std::move(bar)));
template<Bar T, typename Ptr = std::unique_ptr<Dynamic>>
constexpr Ptr make_dyn(T const& bar) noexcept(std::is_nothrow_move_constructible_v<T>)
{
if constexpr(std::is_copy_constructible_v<T>) {
T nbar{bar};
return make_dyn<T, Ptr>(std::move(nbar));
} else {
struct unsafe_ref {
constexpr unsafe_ref(const T& ba) noexcept : b(std::addressof(ba)) {}
constexpr unsafe_ref(const unsafe_ref&) noexcept = default;
constexpr ~unsafe_ref() noexcept = default;
constexpr unsafe_ref(unsafe_ref&& b) noexcept : b(std::exchange(b.b, nullptr)) {}
constexpr unsafe_ref& operator=(unsafe_ref const&) noexcept = default;
constexpr unsafe_ref& operator=(unsafe_ref&& m) noexcept {
if(this != &m)
b = std::exchange(m.b, nullptr);
return *this;
}
const T* b;
constexpr operator T const&() const noexcept { return *b; }
};
unsafe_ref re{bar};
return make_dyn<unsafe_ref, Ptr>(std::move(re));
}
}
template<typename Ptr> requires(requires(std::unique_ptr<Dynamic>&& p) { details::static_pcast<Ptr>(std::move(p)); })
constexpr Ptr make_dyn_for(Bar auto&& bar) noexcept(std::is_nothrow_move_constructible_v<std::remove_reference_t<decltype(bar)>>)
{ return make_dyn<decltype(bar), Ptr>(std::move(bar)); }
constexpr void spin(Bar auto& bar, std::convertible_to<increment_t> auto&& a) { return bar.spin(increment_t(a)); }
struct None {
constexpr None() noexcept = default;
constexpr ~None() noexcept = default;
constexpr void spin(increment_t) noexcept {}
constexpr void spin(increment_t) const noexcept {}
constexpr auto aux() noexcept { return not_string(); }
inline std::string const& aux() const noexcept { return none_; }
constexpr std::string aux(std::convertible_to<std::string> auto&& s) const noexcept { return {}; } //{ return std::exchange(none_, std::string(std::move(s))); }
private:
struct not_string {
constexpr not_string() noexcept= default;
constexpr ~not_string() noexcept= default;
[[gnu::const]]
constexpr not_string& operator+=(std::convertible_to<std::string_view> auto&&) { return *this; }
[[gnu::const]]
constexpr not_string& operator=(std::convertible_to<std::string> auto&&) { return *this; }
inline operator std::string&() noexcept { return none_; }
inline operator std::string const&() const noexcept { return none_; }
};
/*constinit*/ thread_local static inline std::string none_;
};
constexpr inline None disable{};
// A bounded progress-bar
struct Progress {
@ -120,9 +223,14 @@ namespace pr {
virtual ~Progress();
std::string& tag() const noexcept;
inline std::string& aux() noexcept { return tag(); }
inline std::string const& aux() const noexcept { return tag(); }
inline std::string aux(std::convertible_to<std::string> auto&& s)
{ return std::exchange(tag(), std::string(std::move(s))); }
protected:
std::string& tag() noexcept;
const std::string& tag() const noexcept;
public:
FILE* output() const noexcept;
FILE*& output() noexcept;
@ -145,7 +253,7 @@ namespace pr {
~v() noexcept = default;
const v& operator=(fract_t f) const noexcept { p_->percentage(f); return *this; }
operator fract_t() const noexcept { return p_->percentage(); }
operator fract_t() const noexcept { return static_cast<const Progress*>(p_)->percentage(); }
};
return v(this);
}
@ -159,7 +267,76 @@ namespace pr {
std::shared_ptr<_impl> inner_;
};
#ifdef SPINNER
class Spinner {
consteval static auto _GENERATE_MAP(auto const& map, size_t size) noexcept
-> std::array<size_t, 256>
{
static_assert(sizeof(map) == size, "Bad map size");
std::array<size_t, 256> out{};
while( size --> 0 ) out[int(map[size])] = size;
return out;
}
constexpr static increment_t range(increment_t by)
{
if(by < -1.0l) return -1.0l;
else if(by > 1.0l) return 1.0l;
return by;
}
public:
constexpr static inline auto ROUTINE = "|/-\\|/-\\|";
constexpr static inline auto ROUTINE_SIZE = sizeof(ROUTINE);
constexpr static inline auto REVERSE_MAP = _GENERATE_MAP(ROUTINE, ROUTINE_SIZE);
static_assert(ROUTINE_SIZE != sizeof(char*), "Invalid routine size");
constexpr ssize_t range(int sz) noexcept
{
/*if(__builtin_expect(sz < 0, false)) {
std::terminate(); // TODO: How to handle wrapping negatives?? Ugh.
}*/
return ssize_t(sz) % ssize_t(ROUTINE_SIZE);
}
constexpr Spinner(size_t n) noexcept
: cur_(ROUTINE[range(n)]) {}
constexpr Spinner(char c = ROUTINE[0]) noexcept
: cur_(range(REVERSE_MAP[int(c)])) {}
constexpr Spinner(Spinner const&) noexcept = default;
constexpr Spinner(Spinner &&) noexcept = default;
~Spinner();
inline void spin(int by) noexcept
{
operator+=(by);
render();
}
inline void spin(increment_by by) noexcept
{
spin(int(range(by) * increment_t(ROUTINE_SIZE)));
}
inline Spinner& operator+=(int by) noexcept
{
cur_ = ROUTINE[size_t(ssize_t(REVERSE_MAP[int(cur_)]) + range(by))];
return *this;
}
inline Spinner& operator++() noexcept { spin(1); return *this; }
inline Spinner operator++(int) noexcept { Spinner s = *this; ++s; return *this; }
constexpr Spinner& operator=(Spinner&&) noexcept = default;
constexpr Spinner& operator=(Spinner const&) noexcept = default;
constexpr char& character() noexcept { return cur_; }
constexpr char character() const noexcept { return cur_; }
void render(bool flush=true);
private:
char cur_ = '|';
};
//TODO: Spinny bar thing (unbounded)
#endif
/*
struct Bar {

@ -4,6 +4,7 @@
#include "shuffle3.h"
#ifdef __cplusplus
#include "rng/impl.hpp"
extern "C" {
#endif
@ -11,8 +12,12 @@ enum rng_kind {
RNG_KIND_FRNG,
RNG_KIND_DRNG,
RNG_KIND_XORNG,
RNG_KIND_LORENZ,
};
typedef long double rng_st_lorenz_t;
typedef _Complex long double rng_lorenz_t;
typedef struct rng_init
{
enum rng_kind kind;
@ -25,7 +30,12 @@ typedef struct rng_init
} drng;
struct {
uint64_t state[2];
} xorng;
} xorng;
struct {
rng_lorenz_t point;
const rng_st_lorenz_t (* _UNIQUE state)[5];
uint64_t iter;
} lorenz;
} init;
} rng_init_opt;
@ -38,14 +48,15 @@ void rng_free(rng_t ptr);
// Tests
extern void rng_test();
extern void rng_test_spec(rng_t rng);
extern void rng_test_spec(rng_t rng) __attribute__((nonnull(1)));
#ifdef __cplusplus
}
// RNG interfaces
#include <rng/frng.hpp>
#include <rng/drng.hpp>
#include <rng/xoroshiro128plus.hpp>
#include "rng/xoroshiro128plus.hpp"
#include "rng/frng.hpp"
#include "rng/drng.hpp"
#include "rng/lorenz.hpp"
namespace rng {
void test_algo(RNG&& rng);

@ -34,6 +34,7 @@ namespace rng
inline constexpr frng(const std::array<double, 2>& ar) : state(ar){P}
inline constexpr frng(std::array<double, 2>&& ar) : state(ar){P}
inline constexpr frng(const double (&ar)[2]) : state({ar[0], ar[1]}) {P}
constexpr virtual ~frng() = default;
#undef P
inline constexpr double next_double() override { return sample(); }
inline constexpr float next_float() override { return (float)sample(); }

@ -6,6 +6,8 @@
/// Base class for RNG impls
struct RNG {
constexpr RNG() noexcept = default;
virtual unsigned char byte();
virtual void bytes(unsigned char* ptr, std::size_t len);
@ -29,7 +31,12 @@ struct RNG {
inline virtual float next_float() { return (float)sample(); }
inline virtual double next_double() { return sample(); }
virtual ~RNG() = default;
constexpr virtual ~RNG() = default;
//explicit operator rng_t() const noexcept;
//friend operator RNG*(rng_t) noexcept;
protected:
virtual double sample() = 0;
//private:
//struct rng_impl* _UNIQUE _held = nullptr;
};

@ -0,0 +1,2 @@
#pragma once

@ -9,10 +9,11 @@ namespace rng
{
using State = std::array<std::uint64_t, 2>;
#define P D_dprintf("xorng: seeded with (%lu, %lu)", state[0], state[1]);
inline constexpr xoroshiro128plus(std::uint64_t s0, std::uint64_t s1) : state({s0, s1}){P}
inline constexpr xoroshiro128plus(std::array<std::uint64_t, 2>&& ar) : state(ar){P}
inline constexpr xoroshiro128plus(const std::array<std::uint64_t, 2>& ar) : state(ar){P}
inline constexpr xoroshiro128plus(const std::uint64_t (&ar)[2]) : state({ar[0], ar[1]}){P}
inline constexpr xoroshiro128plus(std::uint64_t s0, std::uint64_t s1) : RNG(), state({s0, s1}){P}
inline constexpr xoroshiro128plus(std::array<std::uint64_t, 2>&& ar) : RNG(), state(ar){P}
inline constexpr xoroshiro128plus(const std::array<std::uint64_t, 2>& ar) : RNG(), state(ar){P}
inline constexpr xoroshiro128plus(const std::uint64_t (&ar)[2]) : RNG(), state({ar[0], ar[1]}){P}
inline virtual ~xoroshiro128plus() {}
#undef P
std::uint64_t next_ulong();
using RNG::next_long;
@ -25,5 +26,6 @@ namespace rng
private:
State state;
};
static_assert(std::derived_from<xoroshiro128plus, RNG>, "Wtf???");
}

@ -8,17 +8,20 @@
#include <perc.h>
using fract_t = pr::Progress::fract_t;
namespace {
size_t twidth(int fd = STDIN_FILENO, size_t or = Progress::DEFAULT_WIDTH) noexcept
using namespace pr;
size_t twidth(int fd = STDIN_FILENO, size_t orr = Progress::DEFAULT_WIDTH) noexcept
{
struct winsize w;
if(ioctl(fd, TIOCGWINSZ, &w) == -1) return or;
if(ioctl(fd, TIOCGWINSZ, &w) == -1) return orr;
return size_t(w.ws_col);
}
size_t def_bar_width(size_t tw) noexcept
{
return size_t(std::round(DEFAULT_TERM_FRACT * fract_t(tw)));
return size_t(std::round(Progress::DEFAULT_TERM_FRACT * fract_t(tw)));
}
}
@ -57,9 +60,9 @@ namespace pr {
size_t cf = size_t(std::floor(wf));
auto print = []<typename F> (F f, auto&&... args) {
return fmt::format_to(std::back_inserter(buffer), std::forward<F>(f), std::forward<decltype(args)>(args)...);
};
//auto print = []<typename F> (F f, auto&&... args) {
#define print(...) fmt::format_to(std::back_inserter(buffer), __VA_ARGS__);
//};
buffer.clear();
// Render bar
@ -75,11 +78,12 @@ namespace pr {
fprintf(out, "\r%.*s", int(buffer.size() & INT_MAX), static_cast<const char*>(buffer.data()));
// Flush output stream
if(flush) fflush(out);
#undef print
}
void Progress::spin(increment_t by, bool r, bool f) noexcept
{
auto& inner = *inner_;
inner.fract+=by;
inner.fract=by;
if(inner.fract > 1.0l)
inner.fract=1.0l;
if(r) this->render(f);
@ -111,4 +115,21 @@ namespace pr {
if(inner_)
fputc('\n', inner_->output);
}
#ifdef SPINNER
Spinner::~Spinner() {
//TODO: Add output: if(cur_) fputc('\n', output);
}
void Spinner::render(bool flush) {
/*char arr[] = {
'\r',
cur_,
0,
};
fputs(arr, output);
if(flush) fflush(output);*/
}
#endif
}

@ -1,8 +1,53 @@
#include <stdexcept>
#include <iostream>
#include <cmath>
#include <sys/types.h>
#include <rng/impl.hpp>
#include <error.h>
#include <panic.h>
//#define IMPORT_ONE(NS, NAME) using NAME = NS :: NAME
//IMPORT_ONE(std, ssize_t);
template<typename To, typename T, typename Unit=unsigned char>
static inline To* offset_ptr(T* ptr, ssize_t by) noexcept
{
return reinterpret_cast<To*>(reinterpret_cast<Unit*>(ptr)+by);
}
/*
template<typename To, typename Unit>
offset_ptr(auto* p, std::ssize_t by) -> offset_ptr<To, decltype(p), Unit>;
template<typename To>
offset_ptr(auto* p, std::ssize_t by) -> offset_ptr<To, decltype(p)>;
*/
#ifdef FT_PT_OPERATORS
inline operator RNG*(rng_t ptr) noexcept
{
if(__builtin_expect(!ptr, false)) return nullptr;
#ifdef DEBUG
RNG* op =
#else
return
#endif
reinterpret_cast<RNG*>(offset_ptr<RNG>(ptr, -static_cast<ssize_t>(offsetof(RNG, _held))));
#ifdef DEBUG
if(__builtin_expect(!op, false)) return nullptr;
if(__builtin_expect(op->_held, false)) panic("Invalid rng_t -> RNG conversion");
return ptr;
#endif
}
RNG::operator rng_t() const noexcept
{
return reinterpret_cast<rng_t>(static_cast<RNG*>(this)->_held);
}
#endif
inline unsigned char RNG::byte()
{
return (unsigned char)next_int(255);
@ -48,14 +93,21 @@ std::int64_t RNG::next_long()
}
#include <rng.h>
#include <panic.h>
namespace { // C interface
using namespace std;
#define extract_ptr(ptr) ((RNG*)(ptr))
[[gnu::always_inline, gnu::gnu_inline]]
static inline RNG* extract_ptr(rng_t ptr)
{ return reinterpret_cast<RNG*> (ptr); }
static inline RNG& extract_ref(rng_t rng)
{
return *extract_ptr(rng);
}
template<std::derived_from<RNG> T>
static inline rng_t wrap_ptr(T* ptr) noexcept
{
if(__builtin_expect(!ptr, false)) return nullptr;
return reinterpret_cast<rng_t>(static_cast<RNG*>(ptr));
}
template<typename T>
static inline T* extract_downcast_ptr(rng_t rng)
{
@ -64,29 +116,50 @@ namespace { // C interface
extern "C"
{
rng_t rng_new(rng_init_opt opt)
{
switch(opt.kind)
{
case RNG_KIND_FRNG: return (rng_t) new rng::frng(opt.init.frng.state);
case RNG_KIND_DRNG: return (rng_t) new rng::drng(opt.init.drng.state);
case RNG_KIND_XORNG: return (rng_t) new rng::xoroshiro128plus(opt.init.xorng.state);
case RNG_KIND_FRNG: return wrap_ptr( new rng::frng(opt.init.frng.state) );
case RNG_KIND_DRNG: return wrap_ptr( new rng::drng(opt.init.drng.state) );
case RNG_KIND_XORNG: return wrap_ptr( new rng::xoroshiro128plus(opt.init.xorng.state) );
//case RNG_KIND_LORENX: return static_cast<rng_t>( new rng::lorenzAttractor(opt.init.lorenz); );
default: panic("Unknown RNG init opt: %d", opt.kind);
__builtin_unreachable();
}
return nullptr;
}
void rng_free(rng_t rng)
{
RNG* ptr = (RNG*)rng;
RNG* ptr = extract_ptr(rng);
delete ptr;
}
[[gnu::nonnull(1)]]
void rng_test_spec(rng_t rng)
{
cout << "rng_test_spec:" << endl;
rng::test_algo(std::move(extract_ref(rng)));
auto& ref = extract_ref(rng);
cout << "rng_test_spec (" << typeid(ref).name() << ")..." << std::flush;
if constexpr(!is_noexcept) {
#if EXCEPT
try {
rng::test_algo(std::move(ref));
}
catch(const std::exception& except) {
cout << "\r\r\r\tERROR: " << (except.what() ?: typeid(except).name()) << endl;
rng_free(rng);
throw;
} catch(...) {
cout << "\r\r\r\tERROR" << endl;
rng_free(rng);
throw;
}
#endif
} else rng::test_algo(std::move(ref));
cout << "\r\r\r\tOK" << endl;
rng_free(rng);
}
}
}

Loading…
Cancel
Save