stfu in and output :D (so we can read all those random attractors). some small fixes
This commit is contained in:
parent
bd16762319
commit
b47b8f9490
10 changed files with 316 additions and 108 deletions
|
@ -26,9 +26,9 @@ public:
|
|||
LogInfo("Reading file '%s'\n", filename.c_str());
|
||||
|
||||
stfu::node system;
|
||||
if(!system.read(filename.c_str())) throw std::runtime_error(filename + " could not be opened");
|
||||
if(!system.read(filename)) throw std::runtime_error(filename + " could not be opened");
|
||||
|
||||
kernel = AttractorKernel::createAttractorKernel(system.getChild("AttractorKernel"));
|
||||
kernel = AttractorKernel::createAttractorKernel(system.getChild("attractor_kernel"));
|
||||
projector = Projector::createProjector(system.getChild(system.getValue("Projector")), kernel->getDimension());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,9 @@
|
|||
427057A81475637B00CBE978 /* ImageFormatBMP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ImageFormatBMP.hpp; sourceTree = "<group>"; };
|
||||
427057A91475637B00CBE978 /* ImageFormatPNG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ImageFormatPNG.hpp; sourceTree = "<group>"; };
|
||||
427057AB1475637B00CBE978 /* Tonemapper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tonemapper.hpp; sourceTree = "<group>"; };
|
||||
4299F17414B256F700EDE788 /* std_string_ext.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = std_string_ext.hpp; path = stfu/std_string_ext.hpp; sourceTree = "<group>"; };
|
||||
4299F17814B2579B00EDE788 /* stf_input.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = stf_input.hpp; path = stfu/stf_input.hpp; sourceTree = "<group>"; };
|
||||
4299F17914B2579B00EDE788 /* stf_output.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = stf_output.hpp; path = stfu/stf_output.hpp; sourceTree = "<group>"; };
|
||||
42CEC38414AB797200C3AEDA /* Random.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Random.hpp; sourceTree = "<group>"; };
|
||||
42CEC38614ABB85200C3AEDA /* stf_ext.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = stf_ext.hpp; path = stfu/stf_ext.hpp; sourceTree = "<group>"; };
|
||||
42CEC38714ABC2C000C3AEDA /* UnravelHeart3D.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = UnravelHeart3D.hpp; path = kernels/UnravelHeart3D.hpp; sourceTree = "<group>"; };
|
||||
|
@ -84,9 +87,12 @@
|
|||
01C5709C13B63E2F009D151B /* stf */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4299F17414B256F700EDE788 /* std_string_ext.hpp */,
|
||||
01C5703013B63B78009D151B /* stf.hpp */,
|
||||
42CEC38614ABB85200C3AEDA /* stf_ext.hpp */,
|
||||
01C5703113B63B78009D151B /* stf.cpp */,
|
||||
4299F17814B2579B00EDE788 /* stf_input.hpp */,
|
||||
4299F17914B2579B00EDE788 /* stf_output.hpp */,
|
||||
42CEC38614ABB85200C3AEDA /* stf_ext.hpp */,
|
||||
);
|
||||
name = stf;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace Random {
|
|||
All uniform distributions are inclusive (ie [min, max]).
|
||||
*/
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_arithmetic<T>::value && !std::is_integral<T>::value, T>::type uniform(T min, T max){
|
||||
typename std::enable_if<std::is_arithmetic<T>::value && std::is_floating_point<T>::value, T>::type uniform(T min, T max){
|
||||
return min + (rand() / (T) RAND_MAX) * (max - min);
|
||||
}
|
||||
|
||||
|
|
|
@ -91,6 +91,12 @@ namespace Tonemappers {
|
|||
, colors({{Random::make_vector(-2.0, 4.0, n_planes), Random::make_vector(-2.0, 4.0, n_planes), Random::make_vector(-2.0, 4.0, n_planes)}})
|
||||
{}
|
||||
|
||||
Colorizer(size_t n_planes, stfu::node const & n)
|
||||
: gamma_correctors(n_planes)
|
||||
, gammas(stfu::from_stf<Matrix>(n.getChild("gamma_matrix")))
|
||||
, colors(stfu::from_stf<Matrix>(n.getChild("color_matrix")))
|
||||
{}
|
||||
|
||||
template <typename C>
|
||||
void analyse(C const & canvas){
|
||||
for (size_t i = 0; i < gamma_correctors.size(); ++i) {
|
||||
|
@ -131,10 +137,23 @@ namespace Tonemappers {
|
|||
return node;
|
||||
}
|
||||
|
||||
static Colorizer from_stf(stfu::node const & n) {
|
||||
if (n.getValue("class") != "colorizer") std::cerr << "trying to read an stf as colorizer whil class != \"colorizer\"";
|
||||
auto g = stfu::from_stf<Matrix>(n.getChild("gamma_matrix"));
|
||||
auto c = stfu::from_stf<Matrix>(n.getChild("color_matrix"));
|
||||
|
||||
Colorizer tonemapper(g[0].size());
|
||||
tonemapper.gammas = g;
|
||||
tonemapper.colors = c;
|
||||
|
||||
return tonemapper;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<GammaCorrector> gamma_correctors;
|
||||
std::array<std::vector<double>, 3> gammas;
|
||||
std::array<std::vector<double>, 3> colors;
|
||||
typedef std::array<std::vector<double>, 3> Matrix;
|
||||
Matrix gammas;
|
||||
Matrix colors;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
49
main.cpp
49
main.cpp
|
@ -54,6 +54,16 @@ void render(Attractor & myAttractor, C & canvas, unsigned int iterations){
|
|||
}
|
||||
}
|
||||
|
||||
template <typename C, typename TM>
|
||||
void output(C const & canvas, TM & tonemapper, std::string const & image_path, stfu::node & stf_output){
|
||||
tonemapper.analyse(canvas);
|
||||
|
||||
ImageFormats::png::png_stream image(canvas.template size<0>(), canvas.template size<1>(), image_path + ".png");
|
||||
tonemapper.process(canvas, image);
|
||||
|
||||
stf_output.addChild("tonemapper") = stfu::to_stf(tonemapper);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) try {
|
||||
std::string attractorFile, output_path;
|
||||
unsigned int iterations, width, height;
|
||||
|
@ -74,14 +84,18 @@ int main(int argc, char* argv[]) try {
|
|||
po::notify(vm);
|
||||
|
||||
if (vm.count("help") || argc <= 1) {
|
||||
std::cout << desc << std::endl;
|
||||
//std::cout << desc << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
stfu::node stf_input;
|
||||
stf_input.read(attractorFile);
|
||||
std::string filename = output_path + generate_filename();
|
||||
stfu::node output;
|
||||
|
||||
stfu::node stf_output;
|
||||
Logger logger(std::cout, LOG_VERBOSE);
|
||||
|
||||
|
||||
|
||||
LayeredCanvas<Canvas2D<unsigned int> > canvas(width, height, 2);
|
||||
{
|
||||
Attractor my_attractor(attractorFile);
|
||||
|
@ -97,26 +111,29 @@ int main(int argc, char* argv[]) try {
|
|||
return 0;
|
||||
}
|
||||
|
||||
output = stfu::to_stf(my_attractor);
|
||||
stf_output = stfu::to_stf(my_attractor);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
|
||||
logger.start("analysing canvas and saving image");
|
||||
if(stf_input.getChild("tonemapper").getValue("class") == "colorizer") {
|
||||
Tonemappers::Colorizer tonemapper(canvas.layers(), stf_input.getChild("tonemapper"));
|
||||
output(canvas, tonemapper, filename, stf_output);
|
||||
} else {
|
||||
Tonemappers::Colorizer tonemapper(canvas.layers(), Random::parameters());
|
||||
tonemapper.analyse(canvas);
|
||||
|
||||
logger.start("saving image");
|
||||
ImageFormats::png::png_stream image(canvas.size<0>(), canvas.size<1>(), filename + ".png");
|
||||
tonemapper.process(canvas, image);
|
||||
logger.stop();
|
||||
|
||||
output.addChild("tonemapper") = stfu::to_stf(tonemapper);
|
||||
output(canvas, tonemapper, filename, stf_output);
|
||||
}
|
||||
logger.stop();
|
||||
|
||||
|
||||
|
||||
{
|
||||
output.addValue("version") = __DATE__" "__TIME__;
|
||||
output.addValue("notes") = "This is the version with `cheap` blur and random colours";
|
||||
stf_output.addValue("version") = __DATE__" "__TIME__;
|
||||
stf_output.addValue("notes") = "This is the version with `cheap` blur and random colours";
|
||||
std::string path(filename + ".stf");
|
||||
std::ofstream file(path.c_str());
|
||||
file << output << std::endl;
|
||||
file << stf_output << std::endl;
|
||||
}
|
||||
} catch (std::exception & e) {
|
||||
std::cout << "Terminated because of: " << e.what() << std::endl;
|
||||
|
|
55
stfu/std_string_ext.hpp
Normal file
55
stfu/std_string_ext.hpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// std_string_ext.hpp
|
||||
// AwesomeAttract0r
|
||||
//
|
||||
// Created by Joshua Moerman on 1/2/12.
|
||||
// Copyright (c) 2012 Vadovas. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef AwesomeAttract0r_std_string_ext_hpp
|
||||
#define AwesomeAttract0r_std_string_ext_hpp
|
||||
|
||||
namespace std {
|
||||
template <typename T>
|
||||
std::string to_string(T const & x){
|
||||
std::stringstream ss;
|
||||
ss << x;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
struct from_string_struct {
|
||||
T operator()(string const & s){
|
||||
stringstream ss(s);
|
||||
T t;
|
||||
ss >> t;
|
||||
return t;
|
||||
}
|
||||
};
|
||||
|
||||
#define from_string_fund(x, y) \
|
||||
template <> \
|
||||
struct from_string_struct<x> { \
|
||||
x operator()(string const & s){ return y(s); } \
|
||||
};
|
||||
from_string_fund(float, stof)
|
||||
from_string_fund(double, stod)
|
||||
from_string_fund(long double, stold)
|
||||
from_string_fund(int, stoi)
|
||||
from_string_fund(long int, stol)
|
||||
from_string_fund(long unsigned int, stoul)
|
||||
from_string_fund(long long int, stoll)
|
||||
from_string_fund(long long unsigned int, stoull)
|
||||
from_string_fund(string, )
|
||||
#undef from_string_fund
|
||||
} // namespace
|
||||
|
||||
template <typename T>
|
||||
T from_string(string const & s){
|
||||
return from_string_struct<T>()(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
|
@ -106,7 +106,7 @@ public:
|
|||
\param index If there are > 1 values with the same name, they are indexed. Here you can supply an indexnumber.
|
||||
\return Returns a const std::string& to the value of value with the name and index specified
|
||||
*/
|
||||
const std::string& getValue(const std::string& name, size_t index) const throw(std::out_of_range);
|
||||
const std::string& getValue(const std::string& name, size_t index = 0) const throw(std::out_of_range);
|
||||
|
||||
/**
|
||||
Same as getValue() const, but for unnamed values
|
||||
|
@ -280,6 +280,10 @@ public:
|
|||
*/
|
||||
bool read(const char* filename);
|
||||
|
||||
bool read(std::string const & str){
|
||||
return read(str.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
Writes the STF to an ostream with optional indentation
|
||||
\param out ostream to write to
|
||||
|
|
|
@ -9,91 +9,8 @@
|
|||
#ifndef AwesomeAttract0r_stf_ext_hpp
|
||||
#define AwesomeAttract0r_stf_ext_hpp
|
||||
|
||||
#include <sstream>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include "stf.hpp"
|
||||
|
||||
namespace std {
|
||||
template <typename T>
|
||||
std::string to_string(T const & x){
|
||||
std::stringstream ss;
|
||||
ss << x;
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
|
||||
namespace stfu {
|
||||
|
||||
// Prototypes
|
||||
#define to_stf_container_proto(x) \
|
||||
template <typename T> \
|
||||
typename std::enable_if<std::is_fundamental<T>::value, node>::type to_stf(x<T> const & array); \
|
||||
template <typename T> \
|
||||
typename std::enable_if<!std::is_fundamental<T>::value, node>::type to_stf(x<T> const & array);
|
||||
|
||||
to_stf_container_proto(std::vector)
|
||||
to_stf_container_proto(std::list)
|
||||
|
||||
#undef to_stf_container_proto
|
||||
|
||||
template <typename T, size_t N>
|
||||
typename std::enable_if<std::is_fundamental<T>::value, node>::type to_stf(std::array<T, N> const & array);
|
||||
template <typename T, size_t N>
|
||||
typename std::enable_if<!std::is_fundamental<T>::value, node>::type to_stf(std::array<T, N> const & array);
|
||||
template <typename T>
|
||||
node to_stf(T const & x);
|
||||
|
||||
// implementations
|
||||
#define to_stf_container(x) \
|
||||
template <typename T> \
|
||||
typename std::enable_if<std::is_fundamental<T>::value, node>::type to_stf(x<T> const & array) { \
|
||||
node node; \
|
||||
for (auto it = array.cbegin(); it != array.cend(); ++it){ \
|
||||
node.addValue() = std::to_string(*it); \
|
||||
} \
|
||||
return node; \
|
||||
} \
|
||||
\
|
||||
template <typename T> \
|
||||
typename std::enable_if<!std::is_fundamental<T>::value, node>::type to_stf(x<T> const & array) { \
|
||||
node node; \
|
||||
for (auto it = array.cbegin(); it != array.cend(); ++it){ \
|
||||
node.addChild() = to_stf(*it); \
|
||||
} \
|
||||
return node; \
|
||||
}
|
||||
|
||||
to_stf_container(std::vector)
|
||||
to_stf_container(std::list)
|
||||
|
||||
#undef to_stf_container
|
||||
|
||||
template <typename T, size_t N>
|
||||
typename std::enable_if<std::is_fundamental<T>::value, node>::type to_stf(std::array<T, N> const & array) {
|
||||
node node;
|
||||
for (auto it = array.cbegin(); it != array.cend(); ++it){
|
||||
node.addValue() = std::to_string(*it);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
template <typename T, size_t N>
|
||||
typename std::enable_if<!std::is_fundamental<T>::value, node>::type to_stf(std::array<T, N> const & array) {
|
||||
node node;
|
||||
for (auto it = array.cbegin(); it != array.cend(); ++it){
|
||||
node.addChild() = to_stf(*it);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
node to_stf(T const & x){
|
||||
return x.to_stf();
|
||||
}
|
||||
|
||||
} // namespace stfu
|
||||
#include "stf_input.hpp"
|
||||
#include "stf_output.hpp"
|
||||
|
||||
#endif
|
||||
|
|
105
stfu/stf_input.hpp
Normal file
105
stfu/stf_input.hpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// stf_input.hpp
|
||||
// AwesomeAttract0r
|
||||
//
|
||||
// Created by Joshua Moerman on 1/2/12.
|
||||
// Copyright (c) 2012 Vadovas. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef AwesomeAttract0r_stf_input_hpp
|
||||
#define AwesomeAttract0r_stf_input_hpp
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include "stf.hpp"
|
||||
#include "std_string_ext.hpp"
|
||||
|
||||
namespace stfu {
|
||||
// prototypes
|
||||
template <typename T>
|
||||
T from_stf(node const & n);
|
||||
|
||||
// implemtations
|
||||
namespace {
|
||||
template <typename T, typename Enable = void>
|
||||
struct from_stf_struct {
|
||||
T operator()(node const & n){
|
||||
return T::from_stf(n);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct from_stf_struct<std::vector<T>, typename std::enable_if<!std::is_fundamental<T>::value>::type>;
|
||||
template <typename T>
|
||||
struct from_stf_struct<std::vector<T>, typename std::enable_if<std::is_fundamental<T>::value>::type>;
|
||||
template <typename T, size_t N>
|
||||
struct from_stf_struct<std::array<T, N>, typename std::enable_if<!std::is_fundamental<T>::value>::type>;
|
||||
template <typename T, size_t N>
|
||||
struct from_stf_struct<std::array<T, N>, typename std::enable_if<std::is_fundamental<T>::value>::type>;
|
||||
|
||||
template <typename T>
|
||||
struct from_stf_struct<std::vector<T>, typename std::enable_if<!std::is_fundamental<T>::value>::type>{
|
||||
std::vector<T> operator()(node const & n){
|
||||
std::vector<T> v;
|
||||
bool done = false;
|
||||
for(unsigned int i = 0; !done; ++i){
|
||||
try {
|
||||
v.push_back(from_stf<T>(n.getChild(i)));
|
||||
} catch (std::out_of_range& e) {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct from_stf_struct<std::vector<T>, typename std::enable_if<std::is_fundamental<T>::value>::type>{
|
||||
std::vector<T> operator()(node const & n){
|
||||
std::vector<T> v;
|
||||
bool done = false;
|
||||
for(unsigned int i = 0; !done; ++i){
|
||||
try {
|
||||
v.push_back(std::from_string<T>(n.getValue(i)));
|
||||
} catch (std::out_of_range& e) {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, size_t N>
|
||||
struct from_stf_struct<std::array<T, N>, typename std::enable_if<!std::is_fundamental<T>::value>::type>{
|
||||
std::array<T, N> operator()(node const & n){
|
||||
std::array<T, N> v;
|
||||
for(unsigned int i = 0; i < N; ++i){
|
||||
v[i] = from_stf<T>(n.getChild(i));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, size_t N>
|
||||
struct from_stf_struct<std::array<T, N>, typename std::enable_if<std::is_fundamental<T>::value>::type>{
|
||||
std::array<T, N> operator()(node const & n){
|
||||
std::array<T, N> v;
|
||||
for(unsigned int i = 0; i < N; ++i){
|
||||
v[i] = std::from_string<T>(n.getValue(i));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T from_stf(node const & n){
|
||||
return from_stf_struct<T>()(n);
|
||||
}
|
||||
|
||||
} // namespace stfu
|
||||
|
||||
#endif
|
85
stfu/stf_output.hpp
Normal file
85
stfu/stf_output.hpp
Normal file
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// stf_output.hpp
|
||||
// AwesomeAttract0r
|
||||
//
|
||||
// Created by Joshua Moerman on 1/2/12.
|
||||
// Copyright (c) 2012 Vadovas. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef AwesomeAttract0r_stf_output_hpp
|
||||
#define AwesomeAttract0r_stf_output_hpp
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include "stf.hpp"
|
||||
#include "std_string_ext.hpp"
|
||||
|
||||
namespace stfu {
|
||||
// Prototypes
|
||||
#define to_stf_container_proto(x) \
|
||||
template <typename T> \
|
||||
typename std::enable_if<std::is_fundamental<T>::value, node>::type to_stf(x<T> const & array); \
|
||||
template <typename T> \
|
||||
typename std::enable_if<!std::is_fundamental<T>::value, node>::type to_stf(x<T> const & array);
|
||||
|
||||
to_stf_container_proto(std::vector)
|
||||
|
||||
#undef to_stf_container_proto
|
||||
|
||||
template <typename T, size_t N>
|
||||
typename std::enable_if<std::is_fundamental<T>::value, node>::type to_stf(std::array<T, N> const & array);
|
||||
template <typename T, size_t N>
|
||||
typename std::enable_if<!std::is_fundamental<T>::value, node>::type to_stf(std::array<T, N> const & array);
|
||||
template <typename T>
|
||||
node to_stf(T const & x);
|
||||
|
||||
// implementations
|
||||
#define to_stf_container(x) \
|
||||
template <typename T> \
|
||||
typename std::enable_if<std::is_fundamental<T>::value, node>::type to_stf(x<T> const & array) { \
|
||||
node node; \
|
||||
for (auto it = array.cbegin(); it != array.cend(); ++it) \
|
||||
node.addValue() = std::to_string(*it); \
|
||||
return node; \
|
||||
} \
|
||||
\
|
||||
template <typename T> \
|
||||
typename std::enable_if<!std::is_fundamental<T>::value, node>::type to_stf(x<T> const & array) { \
|
||||
node node; \
|
||||
for (auto it = array.cbegin(); it != array.cend(); ++it) \
|
||||
node.addChild() = to_stf(*it); \
|
||||
return node; \
|
||||
}
|
||||
|
||||
to_stf_container(std::vector)
|
||||
|
||||
#undef to_stf_container
|
||||
|
||||
template <typename T, size_t N>
|
||||
typename std::enable_if<std::is_fundamental<T>::value, node>::type to_stf(std::array<T, N> const & array) {
|
||||
node node;
|
||||
for (auto it = array.cbegin(); it != array.cend(); ++it){
|
||||
node.addValue() = std::to_string(*it);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
template <typename T, size_t N>
|
||||
typename std::enable_if<!std::is_fundamental<T>::value, node>::type to_stf(std::array<T, N> const & array) {
|
||||
node node;
|
||||
for (auto it = array.cbegin(); it != array.cend(); ++it){
|
||||
node.addChild() = to_stf(*it);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
node to_stf(T const & x){
|
||||
return x.to_stf();
|
||||
}
|
||||
|
||||
} // namespace stfu
|
||||
|
||||
#endif
|
Reference in a new issue