1#ifndef DASMIG_ENTITYGEN_HPP
2#define DASMIG_ENTITYGEN_HPP
44 [[nodiscard]]
bool has(
const std::wstring& component_key)
const
46 return _values.contains(component_key);
55 [[nodiscard]] T
get(
const std::wstring& component_key)
const
57 return std::any_cast<T>(_values.at(component_key));
62 [[nodiscard]] effolkronium::random_local&
random()
const
72 std::map<std::wstring, std::any> _values;
75 mutable effolkronium::random_local _random;
95 [[nodiscard]]
virtual std::wstring
key()
const = 0;
108 [[nodiscard]]
virtual std::wstring
to_string(
const std::any& value)
const = 0;
117 [[nodiscard]]
virtual double weight()
const {
return 1.0; }
123 [[nodiscard]]
virtual bool validate([[maybe_unused]]
const std::any& value)
const
149 if (value.type() ==
typeid(std::wstring))
151 return std::any_cast<std::wstring>(value);
153 if (value.type() ==
typeid(
int))
155 return std::to_wstring(std::any_cast<int>(value));
157 if (value.type() ==
typeid(
double))
159 return std::to_wstring(std::any_cast<double>(value));
161 if (value.type() ==
typeid(
float))
163 return std::to_wstring(std::any_cast<float>(value));
165 if (value.type() ==
typeid(
long))
167 return std::to_wstring(std::any_cast<long>(value));
169 if (value.type() ==
typeid(
bool))
171 return std::any_cast<bool>(value) ? L
"true" : L
"false";
189template <
typename T,
typename Formatter = use_default_formatter>
199 : _key{std::move(
key)}, _value{std::move(value)},
200 _fmt{std::move(fmt)} {}
202 [[nodiscard]] std::wstring
key()
const override {
return _key; }
210 [[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
212 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
215 return _fmt(std::any_cast<T>(value));
227template <
typename T,
typename Formatter = use_default_formatter>
238 : _key{std::move(
key)}, _choices{std::move(choices)},
241 if (_choices.empty())
242 throw std::invalid_argument(
"choices must not be empty");
245 [[nodiscard]] std::wstring
key()
const override {
return _key; }
250 return *ctx.
random().get(_choices);
253 [[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
255 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
258 return _fmt(std::any_cast<T>(value));
263 std::vector<T> _choices;
270template <
typename T,
typename Formatter = use_default_formatter>
271 requires std::is_arithmetic_v<T>
282 : _key{std::move(
key)}, _lo{lo}, _hi{hi}, _fmt{std::move(fmt)} {}
284 [[nodiscard]] std::wstring
key()
const override {
return _key; }
289 return ctx.
random().get(_lo, _hi);
292 [[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
294 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
297 return _fmt(std::any_cast<T>(value));
314template <
typename T,
typename GenFn,
typename Formatter = use_default_formatter>
324 : _key{std::move(
key)}, _fn{std::move(fn)}, _fmt{std::move(fmt)} {}
326 [[nodiscard]] std::wstring
key()
const override {
return _key; }
334 [[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
336 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
339 return _fmt(std::any_cast<T>(value));
352template <
typename GenFn,
typename Formatter = use_default_formatter>
354 -> callback_component<
355 std::invoke_result_t<GenFn, const generation_context&>,
364template <
typename T,
typename Formatter = use_default_formatter>
382 std::vector<option> options,
384 : _key{std::move(
key)}, _options{std::move(options)},
387 if (_options.empty())
388 throw std::invalid_argument(
"options must not be empty");
389 if (!std::ranges::any_of(_options,
390 [](
const auto& o) {
return o.weight > 0.0; }))
391 throw std::invalid_argument(
392 "at least one option must have a positive weight");
395 [[nodiscard]] std::wstring
key()
const override {
return _key; }
400 std::vector<double> weights;
401 weights.reserve(_options.size());
402 std::ranges::transform(_options, std::back_inserter(weights),
405 std::discrete_distribution<std::size_t> dist(
406 weights.begin(), weights.end());
407 return _options[dist(ctx.
random().engine())].value;
410 [[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
412 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
415 return _fmt(std::any_cast<T>(value));
420 std::vector<option> _options;
440 template <
typename T>
441 [[nodiscard]] T
get(
const std::wstring& component_key)
const
443 return std::any_cast<T>(find_value(component_key));
450 [[nodiscard]]
const std::any&
get_any(
const std::wstring& component_key)
const
452 return find_value(component_key);
458 [[nodiscard]]
bool has(
const std::wstring& component_key)
const
460 return std::ranges::any_of(_entries, [&component_key](
const auto& e) {
461 return e.key == component_key;
469 [[nodiscard]] std::uint64_t
seed(
const std::wstring& component_key)
const
471 auto it = std::ranges::find(_entries, component_key, &entry::key);
473 if (it == _entries.end())
475 throw std::out_of_range(
"component not found");
485 [[nodiscard]] std::uint64_t
seed()
const
492 [[nodiscard]] std::vector<std::wstring>
keys()
const
494 std::vector<std::wstring> result;
495 result.reserve(_entries.size());
496 std::ranges::transform(_entries, std::back_inserter(result), &entry::key);
502 [[nodiscard]] std::map<std::wstring, std::wstring>
to_map()
const
504 std::map<std::wstring, std::wstring> result;
505 for (
const auto& e : _entries)
507 result.emplace(e.key, e.display);
513 [[nodiscard]] std::size_t
size()
const {
return _entries.size(); }
516 [[nodiscard]]
bool empty()
const {
return _entries.empty(); }
524 auto parts = _entries
525 | std::views::transform([](
const auto& e) {
526 return e.key + L
": " + e.display;
529 return std::ranges::fold_left_first(parts,
530 [](std::wstring acc,
const std::wstring& part) {
531 return std::move(acc) + L
" " + part;
532 }).value_or(std::wstring{});
547 std::wstring display;
556 [[nodiscard]]
const std::any& find_value(
const std::wstring& component_key)
const
558 auto it = std::ranges::find(_entries, component_key, &entry::key);
560 if (it == _entries.end())
562 throw std::out_of_range(
"component not found");
569 std::vector<entry> _entries;
572 std::uint64_t _seed{0};
609 [[maybe_unused]]
const std::any& value) {}
617 virtual void on_skip([[maybe_unused]]
const std::wstring& key) {}
627 [[maybe_unused]] std::size_t attempt) {}
633 [[maybe_unused]] std::size_t attempt,
634 [[maybe_unused]]
const std::any& value) {}
713 eg&
add(std::unique_ptr<component> comp)
715 const auto key = comp->key();
718 auto it = std::ranges::find(_components, key, &component_entry::key);
720 if (it != _components.end())
722 it->comp = std::move(comp);
726 _components.push_back({key, std::move(comp)});
739 eg&
add(std::unique_ptr<component> comp,
double weight_override)
741 const auto key = comp->key();
742 add(std::move(comp));
743 _weight_overrides[key] = weight_override;
752 eg&
weight(
const std::wstring& component_key,
double weight_value)
754 if (!
has(component_key))
756 throw std::out_of_range(
"component not found");
759 _weight_overrides[component_key] = weight_value;
770 std::erase_if(_components, [&component_key](
const auto& entry) {
771 return entry.key == component_key;
773 _weight_overrides.erase(component_key);
782 [[nodiscard]]
bool has(
const std::wstring& component_key)
const
784 return std::ranges::any_of(_components, [&component_key](
const auto& entry) {
785 return entry.key == component_key;
794 _weight_overrides.clear();
800 [[nodiscard]] std::size_t
size()
const {
return _components.size(); }
806 std::vector<std::wstring> keys;
807 keys.reserve(_components.size());
808 std::ranges::transform(_components, std::back_inserter(keys),
809 &component_entry::key);
825 _engine.seed(
static_cast<std::mt19937::result_type
>(seed_value));
836 _engine.seed(std::random_device{}());
853 _validator = std::move(fn);
861 _validator =
nullptr;
872 _max_retries = retries;
886 _observers.push_back(std::move(obs));
895 std::erase(_observers, obs);
918 auto refs = all_component_refs();
919 return generate_with_hooks([&] {
920 return generate_impl(refs, _engine, _max_retries, _observers);
930 auto refs = all_component_refs();
931 std::mt19937 engine{
static_cast<std::mt19937::result_type
>(call_seed)};
932 return generate_with_hooks([&] {
933 return generate_impl(refs, engine, _max_retries, _observers);
946 return generate_with_hooks([&] {
947 return generate_impl(filtered, _engine, _max_retries, _observers);
957 std::uint64_t call_seed)
const
960 std::mt19937 engine{
static_cast<std::mt19937::result_type
>(call_seed)};
961 return generate_with_hooks([&] {
962 return generate_impl(filtered, engine, _max_retries, _observers);
970 return generate(std::span<const std::wstring>{
977 std::uint64_t call_seed)
const
979 return generate(std::span<const std::wstring>{
992 std::vector<entity> entities;
993 entities.reserve(count);
995 auto refs = all_component_refs();
996 std::generate_n(std::back_inserter(entities), count,
998 return generate_with_hooks([&] {
999 return generate_impl(refs, _engine, _max_retries, _observers);
1014 std::size_t count, std::uint64_t call_seed)
const
1016 std::vector<entity> entities;
1017 entities.reserve(count);
1019 auto refs = all_component_refs();
1020 std::mt19937 engine{
static_cast<std::mt19937::result_type
>(call_seed)};
1021 std::generate_n(std::back_inserter(entities), count,
1023 return generate_with_hooks([&] {
1024 return generate_impl(refs, engine, _max_retries, _observers);
1045 auto refs = all_component_refs();
1047 std::vector<std::uint64_t> seeds(count);
1048 std::ranges::generate(seeds,
1049 [&] {
return static_cast<std::uint64_t
>(_engine()); });
1051 std::vector<std::future<entity>> futures;
1052 futures.reserve(count);
1053 std::ranges::transform(seeds, std::back_inserter(futures),
1054 [
this, &refs](
auto task_seed) {
1055 return std::async(std::launch::async,
1056 [
this, &refs, task_seed] {
1057 std::mt19937 engine{
1058 static_cast<std::mt19937::result_type
>(task_seed)};
1059 return generate_with_hooks([&] {
1060 return generate_impl(
1061 refs, engine, _max_retries, _observers);
1066 std::vector<entity> entities;
1067 entities.reserve(count);
1068 std::ranges::transform(futures, std::back_inserter(entities),
1069 [](
auto& f) {
return f.get(); });
1082 std::size_t count, std::uint64_t call_seed)
const
1084 auto refs = all_component_refs();
1086 std::mt19937 seed_engine{
1087 static_cast<std::mt19937::result_type
>(call_seed)};
1088 std::vector<std::uint64_t> seeds(count);
1089 std::ranges::generate(seeds,
1090 [&] {
return static_cast<std::uint64_t
>(seed_engine()); });
1092 std::vector<std::future<entity>> futures;
1093 futures.reserve(count);
1094 std::ranges::transform(seeds, std::back_inserter(futures),
1095 [
this, &refs](
auto task_seed) {
1096 return std::async(std::launch::async,
1097 [
this, &refs, task_seed] {
1098 std::mt19937 engine{
1099 static_cast<std::mt19937::result_type
>(task_seed)};
1100 return generate_with_hooks([&] {
1101 return generate_impl(
1102 refs, engine, _max_retries, _observers);
1107 std::vector<entity> entities;
1108 entities.reserve(count);
1109 std::ranges::transform(futures, std::back_inserter(entities),
1110 [](
auto& f) {
return f.get(); });
1135 _groups.erase(group_name);
1142 [[nodiscard]]
bool has_group(
const std::wstring& group_name)
const
1144 return _groups.contains(group_name);
1153 auto it = _groups.find(group_name);
1154 if (it == _groups.end())
1156 throw std::out_of_range(
"group not found");
1159 auto filtered = filter_components(it->second);
1160 return generate_with_hooks([&] {
1161 return generate_impl(filtered, _engine, _max_retries, _observers);
1171 std::uint64_t call_seed)
const
1173 auto it = _groups.find(group_name);
1174 if (it == _groups.end())
1176 throw std::out_of_range(
"group not found");
1179 auto filtered = filter_components(it->second);
1180 std::mt19937 engine{
static_cast<std::mt19937::result_type
>(call_seed)};
1181 return generate_with_hooks([&] {
1182 return generate_impl(filtered, engine, _max_retries, _observers);
1190 struct component_entry
1193 std::unique_ptr<component> comp;
1197 struct component_ref
1200 std::reference_wrapper<const component> comp;
1201 double effective_weight;
1205 using observer_list =
1206 std::vector<std::shared_ptr<generation_observer>>;
1209 template <
typename Hook,
typename... Args>
1210 static void notify_all(
const observer_list& observers,
1211 Hook hook, Args&&... args)
1213 for (
const auto& obs : observers)
1215 ((*obs).*hook)(std::forward<Args>(args)...);
1220 template <
typename Hook,
typename... Args>
1221 void notify(Hook hook, Args&&... args)
const
1223 notify_all(_observers, hook, std::forward<Args>(args)...);
1227 [[nodiscard]]
static entity generate_impl(
1228 std::span<const component_ref> components, std::mt19937& engine,
1229 std::size_t
max_retries,
const observer_list& observers)
1231 entity generated_entity;
1232 generation_context ctx;
1236 auto entity_seed =
static_cast<std::uint64_t
>(engine());
1237 generated_entity._seed = entity_seed;
1238 std::mt19937 local_engine{
static_cast<std::mt19937::result_type
>(entity_seed)};
1240 for (
const auto& ref : components)
1242 auto component_seed =
static_cast<std::uint64_t
>(local_engine());
1247 if (ref.effective_weight < 1.0)
1249 std::uniform_real_distribution<double> dist(0.0, 1.0);
1250 if (dist(local_engine) >= ref.effective_weight)
1252 notify_all(observers,
1259 if (!ref.comp.get().should_generate(ctx))
1261 notify_all(observers,
1266 notify_all(observers,
1269 ctx._random.seed(
static_cast<std::mt19937::result_type
>(component_seed));
1270 auto value = ref.comp.get().generate(ctx);
1273 for (std::size_t retry = 0;
1274 retry <
max_retries && !ref.comp.get().validate(value);
1277 notify_all(observers,
1279 component_seed =
static_cast<std::uint64_t
>(local_engine());
1281 static_cast<std::mt19937::result_type
>(component_seed));
1282 value = ref.comp.get().generate(ctx);
1283 notify_all(observers,
1288 if (!ref.comp.get().validate(value))
1290 notify_all(observers,
1292 throw std::runtime_error(
1293 "component validation failed after max retries");
1296 auto display = ref.comp.get().to_string(value);
1298 notify_all(observers,
1301 ctx._values[ref.key] = value;
1302 generated_entity._entries.push_back(
1303 {.key = ref.key, .value = std::move(value),
1304 .display = std::move(display),
1305 .seed = component_seed});
1308 return generated_entity;
1314 template <
typename GenFn>
1315 [[nodiscard]] entity with_entity_validation(GenFn&& fn)
const
1317 for (std::size_t attempt = 0; attempt <= _max_retries; ++attempt)
1320 notify_all(_observers,
1324 notify_all(_observers,
1326 if (!_validator || _validator(e))
return e;
1330 throw std::runtime_error(
1331 "entity validation failed after max retries");
1335 template <
typename GenFn>
1336 [[nodiscard]] entity generate_with_hooks(GenFn&& fn)
const
1339 auto e = with_entity_validation(std::forward<GenFn>(fn));
1340 notify_all(_observers,
1348 [[nodiscard]] std::vector<component_ref> all_component_refs()
const
1350 std::vector<component_ref> refs;
1351 refs.reserve(_components.size());
1352 std::ranges::transform(_components, std::back_inserter(refs),
1353 [
this](
const auto& entry) -> component_ref {
1354 return {entry.key, std::cref(*entry.comp),
1355 effective_weight(entry.key, *entry.comp)};
1362 [[nodiscard]] std::vector<component_ref> filter_components(
1365 std::vector<component_ref> filtered;
1367 auto matching = _components
1368 | std::views::filter([&](
const auto& entry) {
1371 | std::views::transform([
this](
const auto& entry) -> component_ref {
1372 return {entry.key, std::cref(*entry.comp),
1373 effective_weight(entry.key, *entry.comp)};
1375 std::ranges::copy(matching, std::back_inserter(filtered));
1382 [[nodiscard]]
double effective_weight(
1383 const std::wstring& key,
const component& comp)
const
1385 auto it = _weight_overrides.find(key);
1386 return it != _weight_overrides.end() ? it->second : comp.weight();
1390 std::vector<component_entry> _components;
1393 std::map<std::wstring, std::vector<std::wstring>> _groups;
1396 std::map<std::wstring, double> _weight_overrides;
1399 std::function<bool(
const entity&)> _validator;
1402 observer_list _observers;
1405 std::size_t _max_retries{10};
1408 std::mt19937 _engine{std::random_device{}()};
Component that wraps a callable for one-off or computed values.
std::any generate(const generation_context &ctx) const override
Generate a random value for this component.
callback_component(std::wstring key, GenFn fn, Formatter fmt={})
Construct a callback component.
std::wstring to_string(const std::any &value) const override
Convert a generated value to a displayable string.
std::wstring key() const override
Unique key identifying this component (e.g. "name", "age").
Component that picks uniformly at random from a list of values.
std::any generate(const generation_context &ctx) const override
Generate a random value for this component.
choice_component(std::wstring key, std::vector< T > choices, Formatter fmt={})
Construct a choice component.
std::wstring key() const override
Unique key identifying this component (e.g. "name", "age").
std::wstring to_string(const std::any &value) const override
Convert a generated value to a displayable string.
Abstract base for entity components.
virtual ~component()=default
Virtual destructor for proper cleanup of derived classes.
virtual std::any generate(const generation_context &ctx) const =0
Generate a random value for this component.
virtual std::wstring to_string(const std::any &value) const =0
Convert a generated value to a displayable string.
virtual bool validate(const std::any &value) const
Validate a generated value.
static std::wstring default_to_string(const std::any &value)
Default conversion covering common standard types.
virtual double weight() const
Inclusion weight for this component (0.0 to 1.0).
virtual std::wstring key() const =0
Unique key identifying this component (e.g. "name", "age").
virtual bool should_generate(const generation_context &ctx) const
Decide whether this component should be generated.
Component that always returns a fixed value.
std::wstring key() const override
Unique key identifying this component (e.g. "name", "age").
constant_component(std::wstring key, T value, Formatter fmt={})
Construct a constant component.
std::any generate(const generation_context &) const override
Generate a random value for this component.
std::wstring to_string(const std::any &value) const override
Convert a generated value to a displayable string.
The entity generator — produces entities with configurable components.
eg & max_retries(std::size_t retries)
Set the maximum number of retries for validation (default 10).
bool has(const std::wstring &component_key) const
Check if a component is registered by key.
entity generate(std::initializer_list< std::wstring > component_keys)
Generate with an initializer list of keys (convenience overload).
eg & unseed()
Reseed the engine with a non-deterministic source.
eg & clear_observers()
Remove all observers.
eg & weight(const std::wstring &component_key, double weight_value)
Set or update the weight override for a registered component.
std::vector< entity > generate_batch(std::size_t count, std::uint64_t call_seed) const
Generate multiple entities using a seed.
entity generate()
Generate an entity with all registered components.
eg & remove_observer(const std::shared_ptr< generation_observer > &obs)
Remove a specific observer by identity.
eg & remove_group(const std::wstring &group_name)
Remove a named group.
entity generate_group(const std::wstring &group_name)
Generate an entity using a named group.
eg & operator=(eg &&)=default
Move assignment.
std::vector< std::wstring > component_keys() const
Return all registered component keys in registration order.
eg & clear_validator()
Remove the entity-level validation function.
bool has_group(const std::wstring &group_name) const
Check if a named group exists.
std::vector< entity > generate_batch_async(std::size_t count)
Generate multiple entities concurrently.
entity generate_group(const std::wstring &group_name, std::uint64_t call_seed) const
Generate an entity using a named group with a seed.
eg(const eg &)=delete
Not copyable.
entity generate(std::uint64_t call_seed) const
Generate an entity with all registered components using a seed.
entity generate(std::initializer_list< std::wstring > component_keys, std::uint64_t call_seed) const
Generate with an initializer list of keys and a seed.
~eg()=default
Default destructor.
eg()=default
Default constructor creates an empty generator.
entity generate(std::span< const std::wstring > component_keys, std::uint64_t call_seed) const
Generate an entity with only the specified components using a seed.
eg & seed(std::uint64_t seed_value)
Seed the internal random engine.
std::vector< entity > generate_batch(std::size_t count)
Generate multiple entities with all registered components.
eg & add(std::unique_ptr< component > comp, double weight_override)
Register a component with a weight override.
eg(eg &&)=default
Move constructor.
eg & add_group(const std::wstring &group_name, std::vector< std::wstring > component_keys)
Register a named group of component keys.
std::vector< entity > generate_batch_async(std::size_t count, std::uint64_t call_seed) const
Generate multiple entities concurrently using a seed.
eg & set_validator(std::function< bool(const entity &)> fn)
Set an entity-level validation function.
static eg & instance()
Access the global singleton instance.
eg & remove(const std::wstring &component_key)
Remove a registered component by key.
eg & clear()
Remove all registered components, weight overrides, and groups.
std::size_t size() const
Return the number of registered components.
eg & add_observer(std::shared_ptr< generation_observer > obs)
Add an observer to receive generation lifecycle callbacks.
entity generate(std::span< const std::wstring > component_keys)
Generate an entity with only the specified components.
eg & operator=(const eg &)=delete
Not copyable.
eg & add(std::unique_ptr< component > comp)
Register a component.
A generated entity holding component values in registration order.
const std::any & get_any(const std::wstring &component_key) const
Type-erased retrieval of a component value by key.
std::size_t size() const
Number of component values in this entity.
T get(const std::wstring &component_key) const
Typed retrieval of a component value by key.
std::uint64_t seed() const
Retrieve the random seed used to generate this entity.
friend std::wostream & operator<<(std::wostream &wos, const entity &e)
Stream all component values in generation order.
std::uint64_t seed(const std::wstring &component_key) const
Retrieve the random seed used to generate a specific component.
bool has(const std::wstring &component_key) const
Check if a component value exists by key.
std::vector< std::wstring > keys() const
Get all component keys present in this entity, in generation order.
std::wstring to_string() const
Convert all component values to a single formatted string.
bool empty() const
Check if the entity has no component values.
std::map< std::wstring, std::wstring > to_map() const
Return component values as a map of key to display string.
Context passed to components during generation.
bool has(const std::wstring &component_key) const
Check if a component value has already been generated.
T get(const std::wstring &component_key) const
Typed retrieval of an already-generated component value.
effolkronium::random_local & random() const
Access the random engine for this generation.
Observer interface for hooking into generation lifecycle events.
virtual void on_after_entity_retry(std::size_t attempt)
Called after an entity validation retry.
virtual void on_after_retry(const std::wstring &key, std::size_t attempt, const std::any &value)
Called after a component validation retry.
virtual void on_before_remove(const std::wstring &key)
Called before a component is removed.
virtual void on_skip(const std::wstring &key)
Called when a component is skipped (weight roll or conditional exclusion).
virtual void on_after_remove(const std::wstring &key)
Called after a component is removed.
virtual void on_before_retry(const std::wstring &key, std::size_t attempt)
Called before a component validation retry.
virtual void on_before_component(const std::wstring &key)
Called before a component is generated.
virtual void on_after_component(const std::wstring &key, const std::any &value)
Called after a component is successfully generated.
virtual void on_after_add(const std::wstring &key)
Called after a component is registered.
virtual void on_before_add(const std::wstring &key)
Called before a component is registered.
virtual void on_before_entity_retry(std::size_t attempt)
Called before an entity validation retry.
virtual ~generation_observer()=default
Virtual destructor.
virtual void on_component_fail(const std::wstring &key)
Called when component validation is exhausted (precedes exception).
virtual void on_before_generate()
Called before an entity is generated.
virtual void on_entity_fail()
Called when entity validation is exhausted (precedes exception).
virtual void on_after_generate(const entity &e)
Called after an entity is successfully generated.
Component that generates a uniform random value in [lo, hi].
range_component(std::wstring key, T lo, T hi, Formatter fmt={})
Construct a range component.
std::wstring to_string(const std::any &value) const override
Convert a generated value to a displayable string.
std::wstring key() const override
Unique key identifying this component (e.g. "name", "age").
std::any generate(const generation_context &ctx) const override
Generate a random value for this component.
Component that picks from a list of values using per-option weights.
std::wstring to_string(const std::any &value) const override
Convert a generated value to a displayable string.
std::any generate(const generation_context &ctx) const override
Generate a random value for this component.
weighted_choice_component(std::wstring key, std::vector< option > options, Formatter fmt={})
Construct a weighted choice component.
std::wstring key() const override
Unique key identifying this component (e.g. "name", "age").
A value–weight pair for weighted selection.
T value
The selectable value.
double weight
Relative selection weight (must be >= 0).