1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
|
// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#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".
*/
VTK_ABI_NAMESPACE_BEGIN
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]);
}
};
VTK_ABI_NAMESPACE_END
#endif
// VTK-HeaderTest-Exclude: vtkPermuteOptions.h
|