You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

314 lines
9.8 KiB
C++

/*=========================================================================
Program: Visualization Toolkit
Module: vtkPermuteOptions.h
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
#ifndef vtkPermuteOptions_h
#define vtkPermuteOptions_h
#include <vtkTimeStamp.h>
#include <cassert>
#include <functional>
#include <sstream>
#include <vector>
/**
* vtkPermuteOptions is a class template designed to exhaustively explore the
* parameter space of a vtkObject subclass.
*
* This testing utility can be taught to update parameters that are defined
* using an API similar to the vtkSetGet macros. Concretely, consider testing
* vtkXMLWriter. This class has a number of independent settings: byte order,
* compressor, data mode, and more. When testing this class, it would be ideal
* to test every combination of these parameters, but this would normally
* require a lot of verbose, redundant, error-prone boilerplate code.
*
* This class simplifies this process. The following describes how to use
* vtkPermuteOptions to run a test using all combinations of vtkXMLWriter's
* byte order and compressor settings (just sticking to two options for
* simplicity -- the class has no limit on number of options or number of
* values for those options).
*
* First, the vtkPermuteOptions object must be instantiated, using the
* configured class as the template parameter:
*
* @code
* vtkPermuteOptions<vtkXMLWriter> config;
* @endcode
*
* Next the options and their possible values are specified. Each call to
* AddOptionValue adds a value to a specific option. Options are created
* automatically as new option names are passed to AddOptionValue. The
* following instructs vtkPermuteOptions to test option ByteOrder (with values
* LittleEndian and BigEndian) and CompressorType (with values NONE, ZLIB, and
* LZ4):
*
* @code
* this->AddOptionValue("ByteOrder", &vtkXMLWriter::SetByteOrder,
* "BigEndian", vtkXMLWriter::BigEndian);
* this->AddOptionValue("ByteOrder", &vtkXMLWriter::SetByteOrder,
* "LittleEndian", vtkXMLWriter::LittleEndian);
*
* this->AddOptionValues("CompressorType", &vtkXMLWriter::SetCompressorType,
* "NONE", vtkXMLWriter::NONE,
* "ZLIB", vtkXMLWriter::ZLIB,
* "LZ4", vtkXMLWriter::LZ4);
* @endcode
*
* Note that that there are two variations on how values may be added to an
* option. For ByteOrder, we use AddOptionValue to specify a human-readable
* string that uniquely identifies the option, a member function pointer to the
* option's setter, a human readable string that uniquely identifies the value,
* and the value itself (in this case, an enum value). The first call creates
* the option named "ByteOrder" and adds the "BigEndian" value. The second call
* adds the "LittleEndian" value to the same option.
*
* The CompressorType call uses the variatic function template AddOptionValues
* to specify multiple values to the same option at once. The value-name and
* value pairs are repeated, and each is added to the option with the supplied
* name. Any number of values may be added to a single option this way.
*
* To run through the permutations, a vtk-esque iterator API is used:
*
* @code
* config.InitPermutations();
* while (!config.IsDoneWithPermutations())
* {
* // Testing code...
*
* // Apply the current option permutation to a vtkXMLWriter object:
* vtkXMLWriter *writer = ...;
* config.ApplyCurrentPermutation(writer);
*
* // More testing code...
*
* config.GoToNextPermutation();
* }
* @endcode
*
* This will repeat the testing code, but configure the vtkXMLWriter object
* differently each time. It will perform a total of 6 iterations, with
* parameters:
*
* @code
* Test Iteration ByteOrder CompressorType
* -------------- --------- --------------
* 1 BigEndian NONE
* 2 BigEndian ZLIB
* 3 BigEndian LZ4
* 4 LittleEndian NONE
* 5 LittleEndian ZLIB
* 6 LittleEndian LZ4
* @endcode
*
* thus exploring the entire parameter space.
*
* A unique, human-readable description of the current configuration can be
* obtained with GetCurrentPermutationName() as long as IsDoneWithPermutations()
* returns false. E.g. the third iteration will be named
* "ByteOrder.BigEndian-CompressorType.LZ4".
*/
template <typename ObjType>
class vtkPermuteOptions
{
using Permutation = std::vector<size_t>;
struct Value
{
Value(const std::string& name, std::function<void(ObjType*)> setter)
: Name(name)
, Setter(setter)
{
}
void Apply(ObjType* obj) const { this->Setter(obj); }
std::string Name; // user-readable option name
std::function<void(ObjType*)> Setter; // Sets the option to a single values
};
struct Option
{
Option(const std::string& name)
: Name(name)
{
}
std::string Name; // user-readable option name
std::vector<Value> Values; // list of values to test for this option
};
std::vector<Option> Options;
std::vector<Permutation> Permutations;
size_t CurrentPermutation;
vtkTimeStamp OptionTime;
vtkTimeStamp PermutationTime;
Option& FindOrCreateOption(const std::string& name)
{
for (Option& opt : this->Options)
{
if (opt.Name == name)
{
return opt;
}
}
this->Options.emplace_back(name);
return this->Options.back();
}
void RecursePermutations(Permutation& perm, size_t level)
{
const size_t maxIdx = this->Options[level].Values.size();
if (level == 0) // base case
{
for (size_t i = 0; i < maxIdx; ++i)
{
perm[0] = i;
this->Permutations.push_back(perm);
}
}
else // recursive case
{
for (size_t i = 0; i < maxIdx; ++i)
{
perm[level] = i;
this->RecursePermutations(perm, level - 1);
}
}
}
void RebuildPermutations()
{
this->Permutations.clear();
const size_t numOptions = this->Options.size();
Permutation perm(numOptions, 0);
this->RecursePermutations(perm, numOptions - 1);
this->PermutationTime.Modified();
}
void Apply(ObjType* obj, const Permutation& perm) const
{
const size_t numOpts = this->Options.size();
assert("Sane permutation" && perm.size() == numOpts);
for (size_t i = 0; i < numOpts; ++i)
{
size_t valIdx = perm[i];
assert("ValueIdx in range" && valIdx < this->Options[i].Values.size());
this->Options[i].Values[valIdx].Apply(obj);
}
}
std::string NamePermutation(const Permutation& perm) const
{
const size_t numOpts = this->Options.size();
assert("Sane permutation" && perm.size() == numOpts);
std::ostringstream out;
for (size_t i = 0; i < numOpts; ++i)
{
size_t valIdx = perm[i];
assert("ValueIdx in range" && valIdx < this->Options[i].Values.size());
out << (i != 0 ? "-" : "") << this->Options[i].Name << "."
<< this->Options[i].Values[valIdx].Name;
}
return out.str();
}
public:
vtkPermuteOptions()
: CurrentPermutation(0)
{
}
template <typename SetterType, typename ValueType>
void AddOptionValue(
const std::string& optionName, SetterType setter, const std::string& valueName, ValueType value)
{
using std::placeholders::_1;
std::function<void(ObjType*)> func = std::bind(setter, _1, value);
Option& opt = this->FindOrCreateOption(optionName);
opt.Values.emplace_back(valueName, func);
this->OptionTime.Modified();
}
template <typename SetterType, typename ValueType>
void AddOptionValues(
const std::string& optionName, SetterType setter, const std::string& valueName, ValueType value)
{
this->AddOptionValue(optionName, setter, valueName, value);
}
template <typename SetterType, typename ValueType, typename... Tail>
void AddOptionValues(const std::string& optionName, SetterType setter,
const std::string& valueName, ValueType value, Tail... tail)
{
this->AddOptionValue(optionName, setter, valueName, value);
this->AddOptionValues(optionName, setter, tail...);
}
void InitPermutations()
{
if (this->OptionTime > this->PermutationTime)
{
this->RebuildPermutations();
}
this->CurrentPermutation = 0;
}
bool IsDoneWithPermutations() const
{
assert("Modified options without resetting permutations." &&
this->PermutationTime > this->OptionTime);
return this->CurrentPermutation >= this->Permutations.size();
}
void GoToNextPermutation()
{
assert("Modified options without resetting permutations." &&
this->PermutationTime > this->OptionTime);
assert("Invalid permutation." && !this->IsDoneWithPermutations());
++this->CurrentPermutation;
}
void ApplyCurrentPermutation(ObjType* obj) const
{
assert("Modified options without resetting permutations." &&
this->PermutationTime > this->OptionTime);
assert("Invalid permutation." && !this->IsDoneWithPermutations());
this->Apply(obj, this->Permutations[this->CurrentPermutation]);
}
std::string GetCurrentPermutationName() const
{
assert("Modified options without resetting permutations." &&
this->PermutationTime > this->OptionTime);
assert("Invalid permutation." && !this->IsDoneWithPermutations());
return this->NamePermutation(this->Permutations[this->CurrentPermutation]);
}
};
#endif
// VTK-HeaderTest-Exclude: vtkPermuteOptions.h