4#ifndef DASMIG_ENTITYGEN_HPP
5#define DASMIG_ENTITYGEN_HPP
40DASMIG_CORE_REQUIRE(1, 1);
55 using array = std::vector<json_value>;
56 using object = std::vector<std::pair<std::string, json_value>>;
67 template <std::same_as<
bool> B>
71 template <std::
integral I>
72 requires(!std::same_as<I, bool>)
76 template <std::
floating_po
int F>
79 const auto x =
static_cast<double>(d);
80 if (std::isfinite(x)) _v = x;
84 [[nodiscard]]
const array*
get_array()
const {
return std::get_if<array>(&_v); }
86 [[nodiscard]]
const object*
get_object()
const {
return std::get_if<object>(&_v); }
88 [[nodiscard]]
object*
get_object() {
return std::get_if<object>(&_v); }
92 void write(core::json_writer& w)
const
96 using X = std::decay_t<
decltype(x)>;
97 if constexpr (std::is_same_v<X, array>)
100 for (
const auto& item : x) item.write(w);
103 else if constexpr (std::is_same_v<X, object>)
106 for (
const auto& [name, item] : x)
122 std::variant<std::nullptr_t, bool, std::int64_t, double, std::string, array, object> _v;
129using input_value = std::variant<bool, std::int64_t, double, std::string>;
132using input_map = std::map<std::string, input_value, std::less<>>;
149 [[nodiscard]]
bool has(
const std::wstring& component_key)
const
151 return find_(component_key) !=
nullptr;
164 template <
typename T>
165 [[nodiscard]] T
get(
const std::wstring& component_key)
const
167 const auto* value = find_(component_key);
168 if (value ==
nullptr)
170 throw std::out_of_range(
"component '" + core::to_utf8(component_key) +
171 "' has not been generated");
173 return std::any_cast<T>(*value);
181 template <
typename T>
182 [[nodiscard]] std::optional<T>
try_get(
const std::wstring& component_key)
const
184 const auto* value = find_(component_key);
185 if (value ==
nullptr)
return std::nullopt;
186 return std::any_cast<T>(*value);
195 template <
typename T>
196 [[nodiscard]] T
get_or(
const std::wstring& component_key, T fallback)
const
198 const auto* value = find_(component_key);
199 return value ==
nullptr ? std::move(fallback) : std::any_cast<T>(*value);
205 [[nodiscard]]
bool has_input(std::string_view name)
const
207 return _inputs !=
nullptr && _inputs->find(name) != _inputs->end();
217 template <
typename T>
218 [[nodiscard]] T
input(std::string_view name)
const
220 const auto it = _inputs ==
nullptr ? input_map::const_iterator{} : _inputs->find(name);
221 if (_inputs ==
nullptr || it == _inputs->end())
223 throw std::invalid_argument(
"input '" + std::string{name} +
"' was not supplied");
225 const T* value = std::get_if<T>(&it->second);
226 if (value ==
nullptr)
228 throw std::invalid_argument(
"input '" + std::string{name} +
"' has another type");
239 template <
typename T>
240 [[nodiscard]] T
input_or(std::string_view name, T fallback)
const
242 return has_input(name) ? input<T>(name) : std::move(fallback);
266 [[nodiscard]]
const std::any* find_(
const std::wstring& component_key)
const
268 const auto it = std::ranges::find(_values, component_key,
269 [](
const auto& v) ->
const std::wstring& {
return *v.first; });
270 return it == _values.end() ? nullptr : it->second;
275 std::vector<std::pair<const std::wstring*, const std::any*>> _values;
281 mutable core::rng _random{0};
307 [[nodiscard]] virtual std::wstring
key() const = 0;
320 [[nodiscard]] virtual std::wstring
to_string(const std::any& value) const = 0;
341 [[nodiscard]]
virtual std::wstring
label()
const {
return key(); }
351 [[nodiscard]]
virtual std::vector<std::wstring>
depends_on()
const {
return {}; }
359 [[nodiscard]]
virtual std::uint64_t
data_hash()
const {
return 0; }
368 [[nodiscard]]
virtual double weight()
const {
return 1.0; }
374 [[nodiscard]]
virtual bool validate([[maybe_unused]]
const std::any& value)
const
401 if (value.type() ==
typeid(std::wstring))
403 return std::any_cast<std::wstring>(value);
405 if (value.type() ==
typeid(
int))
407 return std::to_wstring(std::any_cast<int>(value));
409 if (value.type() ==
typeid(
double))
411 return std::to_wstring(std::any_cast<double>(value));
413 if (value.type() ==
typeid(
float))
415 return std::to_wstring(std::any_cast<float>(value));
417 if (value.type() ==
typeid(
long))
419 return std::to_wstring(std::any_cast<long>(value));
421 if (value.type() ==
typeid(
bool))
423 return std::any_cast<bool>(value) ? L
"true" : L
"false";
425 if (value.type() ==
typeid(std::string))
427 return core::from_utf8(std::any_cast<const std::string&>(value));
429 if (value.type() ==
typeid(
long long))
431 return std::to_wstring(std::any_cast<long long>(value));
433 if (value.type() ==
typeid(
unsigned))
435 return std::to_wstring(std::any_cast<unsigned>(value));
437 if (value.type() ==
typeid(
unsigned long))
439 return std::to_wstring(std::any_cast<unsigned long>(value));
441 if (value.type() ==
typeid(
unsigned long long))
443 return std::to_wstring(std::any_cast<unsigned long long>(value));
454 const std::wstring& display)
456 const auto& type = value.type();
457 if (type ==
typeid(
bool))
return std::any_cast<bool>(value);
458 if (type ==
typeid(
int))
return std::int64_t{std::any_cast<int>(value)};
459 if (type ==
typeid(
long))
return std::int64_t{std::any_cast<long>(value)};
460 if (type ==
typeid(
long long))
return std::int64_t{std::any_cast<long long>(value)};
461 if (type ==
typeid(
unsigned))
return std::int64_t{std::any_cast<unsigned>(value)};
463 if (type ==
typeid(
unsigned long) || type ==
typeid(
unsigned long long))
465 const auto n = type ==
typeid(
unsigned long)
466 ?
static_cast<unsigned long long>(std::any_cast<unsigned long>(value))
467 : std::any_cast<unsigned long long>(value);
468 if (n <=
static_cast<unsigned long long>(std::numeric_limits<std::int64_t>::max()))
469 return static_cast<std::int64_t
>(n);
470 return std::to_string(n);
472 if (type ==
typeid(
double) || type ==
typeid(
float))
474 const double d = type ==
typeid(double) ? std::any_cast<double>(value)
475 :
double{std::any_cast<float>(value)};
476 if (!std::isfinite(d))
return nullptr;
479 if (type ==
typeid(std::wstring))
return core::to_utf8(std::any_cast<std::wstring>(value));
480 if (type ==
typeid(std::string))
return std::any_cast<std::string>(value);
481 return core::to_utf8(display);
496template <
typename T,
typename Formatter = use_default_formatter>
506 : _key{std::move(
key)}, _value{std::move(value)},
507 _fmt{std::move(fmt)} {}
509 [[nodiscard]] std::wstring
key()
const override {
return _key; }
517 [[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
519 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
522 return _fmt(std::any_cast<T>(value));
534template <
typename T,
typename Formatter = use_default_formatter>
545 : _key{std::move(
key)}, _choices{std::move(choices)},
548 if (_choices.empty())
549 throw std::invalid_argument(
"choices must not be empty");
552 [[nodiscard]] std::wstring
key()
const override {
return _key; }
557 return core::pick(ctx.
random(), _choices);
560 [[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
562 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
565 return _fmt(std::any_cast<T>(value));
570 std::vector<T> _choices;
578template <
typename T,
typename Formatter = use_default_formatter>
579 requires(std::integral<T> && !std::same_as<T, bool>) || std::floating_point<T>
590 : _key{std::move(key)}, _lo{lo}, _hi{hi}, _fmt{std::move(fmt)} {}
592 [[nodiscard]] std::wstring
key()
const override {
return _key; }
597 if constexpr (std::floating_point<T>)
599 auto v =
static_cast<T
>(core::uniform_real(ctx.
random(),
static_cast<double>(_lo),
600 static_cast<double>(_hi)));
602 if (v >= _hi && _hi > _lo) v = std::nextafter(_hi, _lo);
607 return core::uniform_int(ctx.
random(), _lo, _hi);
611 [[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
613 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
614 return default_to_string(value);
616 return _fmt(std::any_cast<T>(value));
633template <
typename T,
typename GenFn,
typename Formatter = use_default_formatter>
643 : _key{std::move(
key)}, _fn{std::move(fn)}, _fmt{std::move(fmt)} {}
645 [[nodiscard]] std::wstring
key()
const override {
return _key; }
653 [[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
655 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
658 return _fmt(std::any_cast<T>(value));
671template <
typename GenFn,
typename Formatter = use_default_formatter>
673 -> callback_component<
674 std::invoke_result_t<GenFn, const generation_context&>,
705 _label = std::move(text);
706 return std::move(*
this);
712 _depends = std::move(keys);
713 return std::move(*
this);
719 _format = std::move(fn);
720 return std::move(*
this);
726 _json = std::move(fn);
727 return std::move(*
this);
734 return std::move(*
this);
741 _when = std::move(fn);
742 return std::move(*
this);
748 _validate = std::move(fn);
749 return std::move(*
this);
756 return std::move(*
this);
759 [[nodiscard]] std::wstring
key()
const override {
return _key; }
760 [[nodiscard]] std::wstring
label()
const override {
return _label.value_or(_key); }
761 [[nodiscard]] std::vector<std::wstring>
depends_on()
const override {
return _depends; }
762 [[nodiscard]] std::uint64_t
data_hash()
const override {
return _data_hash; }
763 [[nodiscard]]
double weight()
const override {
return _weight; }
767 return std::any{_fn(ctx)};
770 [[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
772 return _format ? _format(std::any_cast<const T&>(value)) :
default_to_string(value);
777 return _json ? _json(std::any_cast<const T&>(value))
781 [[nodiscard]]
bool validate(
const std::any& value)
const override
783 return !_validate || _validate(std::any_cast<const T&>(value));
788 return !_when || _when(ctx);
794 std::optional<std::wstring> _label;
795 std::vector<std::wstring> _depends;
796 std::function<std::wstring(
const T&)> _format;
799 std::function<bool(
const T&)> _validate;
801 std::uint64_t _data_hash{0};
809template <
typename Fn>
810[[nodiscard]]
auto field(std::wstring key, Fn fn)
813 std::move(key), std::move(fn)};
823template <
typename T,
typename Formatter = use_default_formatter>
842 std::vector<option> options,
844 : _key{std::move(
key)}, _options{std::move(options)},
845 _fmt{std::move(fmt)}, _dist{weights_of(_options)}
849 [[nodiscard]] std::wstring
key()
const override {
return _key; }
854 return _options[_dist(ctx.
random())].value;
857 [[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
859 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
862 return _fmt(std::any_cast<T>(value));
868 static std::vector<double> weights_of(
const std::vector<option>& options)
871 throw std::invalid_argument(
"options must not be empty");
872 if (!std::ranges::all_of(options, [](
const auto& o) {
873 return std::isfinite(o.weight) && o.weight >= 0.0;
875 throw std::invalid_argument(
"option weights must be finite and non-negative");
876 if (!std::ranges::any_of(options, [](
const auto& o) {
return o.weight > 0.0; }))
877 throw std::invalid_argument(
878 "at least one option must have a positive weight");
879 std::vector<double> weights;
880 weights.reserve(options.size());
881 std::ranges::transform(options, std::back_inserter(weights), &
option::weight);
886 std::vector<option> _options;
888 core::discrete _dist;
907 template <
typename T>
908 [[nodiscard]] T
get(
const std::wstring& component_key)
const
910 return std::any_cast<T>(find_value(component_key));
917 [[nodiscard]]
const std::any&
get_any(
const std::wstring& component_key)
const
919 return find_value(component_key);
925 [[nodiscard]]
bool has(
const std::wstring& component_key)
const
927 return std::ranges::any_of(_entries, [&component_key](
const auto& e) {
928 return e.key == component_key;
936 [[nodiscard]] std::uint64_t
seed(
const std::wstring& component_key)
const
938 auto it = std::ranges::find(_entries, component_key, &entry::key);
940 if (it == _entries.end())
942 throw std::out_of_range(
"component not found");
956 [[nodiscard]] std::uint64_t
seed()
const
968 return {.seed = _seed, .data = _data};
982 for (
const auto& e : _entries) insert_path(root, core::to_utf8(e.key), e.json);
983 w.begin_object().key(
"components");
1001 [[nodiscard]] std::vector<std::wstring>
keys()
const
1003 std::vector<std::wstring> result;
1004 result.reserve(_entries.size());
1005 std::ranges::transform(_entries, std::back_inserter(result), &entry::key);
1011 [[nodiscard]] std::map<std::wstring, std::wstring>
to_map()
const
1013 std::map<std::wstring, std::wstring> result;
1014 for (
const auto& e : _entries)
1016 result.emplace(e.key, e.display);
1022 [[nodiscard]] std::size_t
size()
const {
return _entries.size(); }
1025 [[nodiscard]]
bool empty()
const {
return _entries.empty(); }
1035 std::wstring result;
1036 for (
const auto& e : _entries)
1038 if (!result.empty()) result += L
" ";
1039 result += e.label + L
": " + e.display;
1057 std::wstring display;
1067 [[nodiscard]]
const std::any& find_value(
const std::wstring& component_key)
const
1069 auto it = std::ranges::find(_entries, component_key, &entry::key);
1071 if (it == _entries.end())
1073 throw std::out_of_range(
"component not found");
1082 static void insert_path(
json_value::object& root, std::string_view path,
const json_value& value)
1085 for (
auto dot = path.find(
'.'); dot != std::string_view::npos; dot = path.find(
'.'))
1087 const std::string_view name = path.substr(0, dot);
1088 path.remove_prefix(dot + 1);
1089 auto it = std::ranges::find(*level, name, &json_value::object::value_type::first);
1090 if (it == level->end())
1093 it = std::prev(level->end());
1095 level = it->second.get_object();
1096 if (level ==
nullptr)
throw std::logic_error(
"component keys collide in the JSON");
1098 level->emplace_back(std::string{path}, value);
1102 std::vector<entry> _entries;
1105 std::uint64_t _seed{0};
1108 std::uint64_t _data{0};
1151 [[maybe_unused]]
const std::any& value) {}
1159 virtual void on_skip([[maybe_unused]]
const std::wstring& key) {}
1169 [[maybe_unused]] std::size_t attempt) {}
1175 [[maybe_unused]] std::size_t attempt,
1176 [[maybe_unused]]
const std::any& value) {}
1262 eg& operator=(
eg&&) noexcept = default;
1288 eg&
add(std::unique_ptr<component> comp)
1290 const auto key = comp->key();
1294 auto it = std::ranges::find(_components, key, &component_entry::key);
1296 if (it != _components.end())
1298 it->comp = std::move(comp);
1302 _components.push_back({.key = key, .comp = std::move(comp)});
1314 template <
typename T>
1335 return add_recipe_(core::tsv_document::load(path));
1368 return add_recipe_(core::tsv_document::parse(text, std::move(source)));
1377 eg&
add(std::unique_ptr<component> comp,
double weight_override)
1379 const auto key = comp->key();
1380 add(std::move(comp));
1381 _weight_overrides[key] = weight_override;
1390 eg&
weight(
const std::wstring& component_key,
double weight_value)
1392 if (!
has(component_key))
1394 throw std::out_of_range(
"component not found");
1397 _weight_overrides[component_key] = weight_value;
1408 std::erase_if(_components, [&component_key](
const auto& entry) {
1409 return entry.key == component_key;
1411 _weight_overrides.erase(component_key);
1420 [[nodiscard]]
bool has(
const std::wstring& component_key)
const
1422 return std::ranges::any_of(_components, [&component_key](
const auto& entry) {
1423 return entry.key == component_key;
1431 _components.clear();
1432 _weight_overrides.clear();
1438 [[nodiscard]] std::size_t
size()
const {
return _components.size(); }
1444 std::vector<std::wstring> keys;
1445 keys.reserve(_components.size());
1446 std::ranges::transform(_components, std::back_inserter(keys),
1447 &component_entry::key);
1462 _max_retries = retries;
1476 _observers.push_back(std::move(obs));
1485 std::erase(_observers, obs);
1511 const auto refs = resolve_components(request);
1512 return generate_seeded(refs, _engine(), request);
1528 std::uint64_t call_seed)
const
1530 return generate_seeded(resolve_components(request), call_seed, request);
1547 const auto refs = resolve_components(request);
1548 return generate_batch_seeded(refs, request, _engine());
1567 return generate_batch_seeded(resolve_components(request), request, call_seed);
1582 const core::batch_options& options)
1603 const core::batch_options& options,
1604 std::uint64_t batch_seed)
const
1606 const auto refs = resolve_components(request);
1607 return core::run_batch(options, batch_seed, [&](std::uint64_t item_seed)
1608 {
return generate_seeded(refs, item_seed, request); });
1622 template <
typename Sink>
1624 std::uint64_t batch_seed, Sink&& sink)
const
1626 const auto refs = resolve_components(request);
1627 core::for_each_batch(options, batch_seed,
1628 [&](std::uint64_t item_seed) {
return generate_seeded(refs, item_seed, request); },
1629 std::forward<Sink>(sink));
1638 template <
typename Sink>
1642 for_each_entity(request, options, _engine(), std::forward<Sink>(sink));
1664 const auto refs = resolve_components(request);
1665 return generate_batch_async_seeded(refs, request, _engine());
1680 return generate_batch_async_seeded(resolve_components(request), request, call_seed);
1703 _groups.erase(group_name);
1710 [[nodiscard]]
bool has_group(
const std::wstring& group_name)
const
1712 return _groups.contains(group_name);
1723 require_group(request);
1735 std::uint64_t call_seed)
const
1737 require_group(request);
1738 return generate(request, call_seed);
1745 struct component_entry
1748 std::unique_ptr<component> comp;
1752 struct component_ref
1755 std::reference_wrapper<const component> comp;
1756 double effective_weight;
1757 std::vector<std::wstring> depends;
1761 using observer_list =
1762 std::vector<std::shared_ptr<generation_observer>>;
1767 template <
typename Hook,
typename... Args>
1768 static void notify_all(
const observer_list& observers,
1769 Hook hook,
const Args&... args)
1771 for (
const auto& obs : observers)
1773 ((*obs).*hook)(args...);
1778 template <
typename Hook,
typename... Args>
1779 void notify(Hook hook,
const Args&... args)
const
1781 notify_all(_observers, hook, args...);
1786#if defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_PTHREADS__)
1787 static constexpr std::launch async_policy = std::launch::deferred;
1789 static constexpr std::launch async_policy = std::launch::async;
1793 [[nodiscard]]
static std::vector<std::uint64_t> batch_seeds(
1794 std::size_t count, std::uint64_t call_seed)
1796 std::vector<std::uint64_t> seeds(count);
1797 for (std::size_t i = 0; i < count; ++i)
1799 seeds[i] = core::item_seed(call_seed, i);
1807 [[nodiscard]] entity generate_seeded(std::span<const component_ref> refs,
1808 std::uint64_t call_seed,
1809 const entity_request& request)
const
1811 const auto& validator = request.validator;
1813 auto attempts = core::rng{call_seed}.split(
"attempt");
1814 std::optional<entity> accepted;
1815 for (std::size_t attempt = 0; attempt <= _max_retries; ++attempt)
1818 notify_all(_observers,
1820 const std::uint64_t entity_seed = attempt == 0 ? call_seed : attempts();
1821 auto e = generate_impl(refs, entity_seed, _max_retries, _observers, request.inputs);
1823 notify_all(_observers,
1825 if (!validator || validator(e))
1827 accepted = std::move(e);
1835 throw std::runtime_error(
1836 "entity validation failed after max retries");
1839 return std::move(*accepted);
1846 [[nodiscard]]
static entity generate_impl(
1847 std::span<const component_ref> components, std::uint64_t entity_seed,
1848 std::size_t
max_retries,
const observer_list& observers,
const input_map& inputs)
1850 entity generated_entity;
1851 generation_context ctx;
1852 ctx._inputs = &inputs;
1853 generated_entity._seed = entity_seed;
1854 generated_entity._entries.reserve(components.size());
1855 ctx._values.reserve(components.size());
1856 const core::rng root{entity_seed};
1858 for (
const auto& ref : components)
1860 const auto stream = root.split(core::to_utf8(ref.key));
1863 if (!std::ranges::all_of(ref.depends, [&ctx](
const auto& dep) { return ctx.has(dep); }))
1865 notify_all(observers,
1871 if (ref.effective_weight < 1.0)
1873 auto roll = stream.split(
"weight");
1874 if (core::uniform01(roll) >= ref.effective_weight)
1876 notify_all(observers,
1883 if (!ref.comp.get().should_generate(ctx))
1885 notify_all(observers,
1890 notify_all(observers,
1893 auto seeds = stream.split(
"seed");
1894 auto component_seed = seeds();
1895 ctx._random = core::rng{component_seed};
1896 auto value = ref.comp.get().generate(ctx);
1899 for (std::size_t retry = 0;
1900 retry <
max_retries && !ref.comp.get().validate(value);
1903 notify_all(observers,
1905 component_seed = seeds();
1906 ctx._random = core::rng{component_seed};
1907 value = ref.comp.get().generate(ctx);
1908 notify_all(observers,
1913 if (!ref.comp.get().validate(value))
1915 notify_all(observers,
1917 throw std::runtime_error(
1918 "component validation failed after max retries");
1921 auto display = ref.comp.get().to_string(value);
1922 auto json = ref.comp.get().to_json(value);
1924 notify_all(observers,
1927 const auto& added = generated_entity._entries.emplace_back(
1928 entity::entry{.key = ref.key, .label = ref.comp.get().label(),
1929 .value = std::move(value),
1930 .display = std::move(display),
1931 .seed = component_seed, .json = std::move(json)});
1932 ctx._values.emplace_back(&added.key, &added.value);
1935 generated_entity._data = replay_data(components, ctx, inputs);
1936 return generated_entity;
1941 static void require_group(
const entity_request& request)
1944 throw std::invalid_argument(
"generate_group requires a group");
1947 [[nodiscard]] std::vector<component_ref> resolve_components(
1948 const entity_request& request)
const
1950 if (request.group && request.component_keys)
1951 throw std::invalid_argument(
"group and component_keys are mutually exclusive");
1954 const auto it = _groups.find(*request.group);
1955 if (it == _groups.end())
1956 throw std::out_of_range(
"group not found");
1957 return with_dependencies(filter_components(it->second));
1959 return with_dependencies(request.component_keys ? filter_components(*request.component_keys)
1960 : all_component_refs());
1963 [[nodiscard]] std::vector<entity> generate_batch_seeded(
1964 std::span<const component_ref> refs,
const entity_request& request,
1965 std::uint64_t call_seed)
const
1967 std::vector<entity> entities;
1968 entities.reserve(request.count);
1969 for (
const auto entity_seed : batch_seeds(request.count, call_seed))
1970 entities.push_back(generate_seeded(refs, entity_seed, request));
1974 [[nodiscard]] std::vector<entity> generate_batch_async_seeded(
1975 std::span<const component_ref> refs,
const entity_request& request,
1976 std::uint64_t call_seed)
const
1978 std::vector<std::future<entity>> futures;
1979 futures.reserve(request.count);
1980 for (
const auto entity_seed : batch_seeds(request.count, call_seed))
1982 futures.push_back(std::async(async_policy, [
this, refs, &request, entity_seed] {
1983 return generate_seeded(refs, entity_seed, request);
1987 std::vector<entity> entities;
1988 entities.reserve(request.count);
1989 std::ranges::transform(futures, std::back_inserter(entities),
1990 [](
auto& f) {
return f.get(); });
1995 [[nodiscard]] std::vector<component_ref> all_component_refs()
const
1997 std::vector<component_ref> refs;
1998 refs.reserve(_components.size());
1999 std::ranges::transform(_components, std::back_inserter(refs),
2000 [
this](
const auto& entry) -> component_ref {
2001 return make_ref(entry);
2008 [[nodiscard]] std::vector<component_ref> filter_components(
2011 std::vector<component_ref> filtered;
2013 auto matching = _components
2014 | std::views::filter([&](
const auto& entry) {
2017 | std::views::transform([
this](
const auto& entry) -> component_ref {
2018 return make_ref(entry);
2020 std::ranges::copy(matching, std::back_inserter(filtered));
2025 [[nodiscard]] component_ref make_ref(
const component_entry& entry)
const
2027 return {entry.key, std::cref(*entry.comp), effective_weight(entry.key, *entry.comp),
2028 entry.comp->depends_on()};
2035 [[nodiscard]] std::vector<component_ref> with_dependencies(std::vector<component_ref> selected)
const
2037 if (std::ranges::all_of(selected, [](
const auto& r) {
return r.depends.empty(); }))
2040 std::set<std::wstring> wanted;
2041 std::vector<std::wstring> pending;
2042 for (
const auto& r : selected)
2044 if (wanted.insert(r.key).second) pending.push_back(r.key);
2046 for (std::size_t i = 0; i < pending.size(); ++i)
2048 const auto it = std::ranges::find(_components, pending[i], &component_entry::key);
2049 for (
const auto& dep : it->comp->depends_on())
2052 throw std::invalid_argument(
"component '" + core::to_utf8(pending[i]) +
2053 "' depends on '" + core::to_utf8(dep) +
2054 "', which is not registered");
2055 if (wanted.insert(dep).second) pending.push_back(dep);
2059 std::vector<component_ref> remaining;
2060 for (
const auto& entry : _components)
2062 if (wanted.contains(entry.key)) remaining.push_back(make_ref(entry));
2064 std::vector<component_ref> ordered;
2065 std::set<std::wstring> emitted;
2066 while (!remaining.empty())
2068 const auto next = std::ranges::find_if(remaining, [&emitted](
const auto& r) {
2069 return std::ranges::all_of(r.depends, [&emitted](
const auto& d) { return emitted.contains(d); });
2071 if (next == remaining.end())
2074 for (
const auto& r : remaining) names += (names.empty() ?
"'" :
", '") + core::to_utf8(r.key) +
"'";
2075 throw std::logic_error(
"components depend on each other in a cycle: " + names);
2077 emitted.insert(next->key);
2078 ordered.push_back(std::move(*next));
2079 remaining.erase(next);
2086 [[nodiscard]]
static std::uint64_t replay_data(std::span<const component_ref> components,
2087 const generation_context& ctx,
2088 const input_map& inputs)
2091 const auto put_u64 = [&bytes](std::uint64_t n) {
2092 for (
int shift = 0; shift < 64; shift += 8) bytes.push_back(
static_cast<char>((n >> shift) & 0xFFU));
2094 for (
const auto& [name, value] : inputs)
2097 put_u64(name.size());
2099 bytes +=
static_cast<char>(value.index());
2101 [&](
const auto& v) {
2102 using V = std::decay_t<
decltype(v)>;
2103 if constexpr (std::is_same_v<V, bool>) bytes += v ?
'\1' :
'\0';
2104 else if constexpr (std::is_same_v<V, std::int64_t>) put_u64(
static_cast<std::uint64_t
>(v));
2105 else if constexpr (std::is_same_v<V, double>) put_u64(std::bit_cast<std::uint64_t>(v));
2114 for (
const auto& ref : components)
2116 const auto hash = ref.comp.get().data_hash();
2117 if (hash == 0 || !ctx.has(ref.key))
continue;
2118 const auto key = core::to_utf8(ref.key);
2120 put_u64(key.size());
2124 return bytes.empty() ? 0 : core::fnv1a64(bytes);
2130 eg& add_recipe_(
const core::tsv_document& doc)
2132 std::vector<std::unique_ptr<component>> built;
2133 std::set<std::string> seen;
2134 for (
const auto& row : doc.rows())
2136 const auto key = row.get<std::string>(
"key");
2137 if (!seen.insert(key).second)
throw row.error(
"key",
"key repeated in the recipe");
2138 built.push_back(recipe_component_(row, key));
2140 for (
const auto& comp : built) check_key(comp->key());
2141 for (
auto& comp : built)
add(std::move(comp));
2145 static std::unique_ptr<component> recipe_component_(
const core::tsv_row& row,
const std::string& key)
2147 const auto kind = row.get<std::string>(
"kind");
2148 const auto values = row.get<std::string>(
"values");
2149 const auto hash = core::fnv1a64(key +
'\t' + kind +
'\t' + values);
2151 if (kind ==
"constant")
2153 return finish_recipe_(
field(core::from_utf8(key),
2154 [text = core::from_utf8(values)](
const generation_context&) {
return text; }),
2157 if (kind ==
"choice")
2159 std::vector<std::wstring> choices;
2160 for (
const auto item : row.list(
"values",
'|')) choices.push_back(core::from_utf8(item));
2161 return finish_recipe_(
field(core::from_utf8(key),
2162 [choices](
const generation_context& ctx)
2163 {
return core::pick(ctx.random(), choices); }),
2166 if (kind ==
"weighted")
2168 std::vector<std::wstring> choices;
2169 std::vector<double> weights;
2170 for (
const auto item : row.list(
"values",
'|'))
2172 const auto colon = item.rfind(
':');
2173 if (colon == std::string_view::npos)
2174 throw row.error(
"values",
"expected value:weight, got '" + std::string{item} +
"'");
2175 const auto weight = core::detail::parse_number<double>(item.substr(colon + 1));
2177 throw row.error(
"values",
"weight must be a number of 0 or more in '" + std::string{item} +
"'");
2178 choices.push_back(core::from_utf8(item.substr(0, colon)));
2179 weights.push_back(*
weight);
2181 if (!std::ranges::any_of(weights, [](
double w) {
return w > 0.0; }))
2182 throw row.error(
"values",
"at least one weight must be positive");
2183 return finish_recipe_(
field(core::from_utf8(key),
2184 [choices, dist = core::discrete{weights}](
const generation_context& ctx)
2185 {
return choices[dist(ctx.random())]; }),
2188 if (kind ==
"int" || kind ==
"real")
2190 const std::string_view range = values;
2191 const auto dots = range.find(
"..");
2192 if (dots == std::string_view::npos)
2193 throw row.error(
"values",
"expected lo..hi, got '" + values +
"'");
2196 const auto lo = core::detail::parse_number<std::int64_t>(range.substr(0, dots));
2197 const auto hi = core::detail::parse_number<std::int64_t>(range.substr(dots + 2));
2198 if (!lo || !hi || *lo > *hi)
2199 throw row.error(
"values",
"expected whole numbers lo..hi with lo <= hi, got '" + values +
"'");
2200 return finish_recipe_(
field(core::from_utf8(key),
2201 [lo = *lo, hi = *hi](
const generation_context& ctx)
2202 {
return core::uniform_int(ctx.random(), lo, hi); }),
2205 const auto lo = core::detail::parse_number<double>(range.substr(0, dots));
2206 const auto hi = core::detail::parse_number<double>(range.substr(dots + 2));
2207 if (!lo || !hi || !(*lo <= *hi))
2208 throw row.error(
"values",
"expected finite numbers lo..hi with lo <= hi, got '" + values +
"'");
2209 return finish_recipe_(
field(core::from_utf8(key),
2210 [lo = *lo, hi = *hi](
const generation_context& ctx)
2212 const double v = core::uniform_real(ctx.random(), lo, hi);
2213 return v >= hi && hi > lo ? std::nextafter(hi, lo) : v;
2217 throw row.error(
"kind",
"expected constant, choice, weighted, int or real, got '" + kind +
"'");
2221 template <
typename T>
2222 static std::unique_ptr<component> finish_recipe_(field_component<T> f,
const core::tsv_row& row,
2223 const std::string& key, std::uint64_t hash)
2225 const auto label = row.get_or<std::string>(
"label", key);
2226 std::vector<std::wstring> depends;
2227 if (row.has(
"depends_on"))
2229 for (
const auto item : row.list(
"depends_on")) depends.push_back(core::from_utf8(item));
2231 const auto weight = row.get_or<
double>(
"weight", 1.0);
2232 if (!(
weight >= 0.0 &&
weight <= 1.0))
throw row.error(
"weight",
"must be between 0 and 1");
2233 return std::make_unique<field_component<T>>(std::move(f)
2234 .label(core::from_utf8(label))
2235 .depends_on(std::move(depends))
2240 void check_key(
const std::wstring& key)
const
2242 const auto path = core::to_utf8(key);
2243 const auto bad = [&path](
const std::string& why) {
2244 return std::invalid_argument(
"component key '" + path +
"' " + why);
2246 if (!path.empty() && (path.front() ==
'.' || path.back() ==
'.' || path.find(
"..") != std::string::npos))
2247 throw bad(
"has an empty path segment");
2248 for (
const auto& other : _components)
2250 if (other.key == key)
continue;
2251 const auto o = core::to_utf8(other.key);
2252 if (o.starts_with(path +
".") || path.starts_with(o +
"."))
2253 throw bad(
"collides with '" + o +
"' (one is the parent of the other)");
2259 [[nodiscard]]
double effective_weight(
2260 const std::wstring& key,
const component& comp)
const
2262 auto it = _weight_overrides.find(key);
2263 return it != _weight_overrides.end() ? it->second : comp.weight();
2267 std::vector<component_entry> _components;
2270 std::map<std::wstring, std::vector<std::wstring>> _groups;
2273 std::map<std::wstring, double> _weight_overrides;
2276 observer_list _observers;
2279 std::size_t _max_retries{10};
2282 core::rng _engine{core::rng::from_entropy()};
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.
component()=default
Default constructor.
virtual std::wstring label() const
Display name of this component, used by entity::to_string().
virtual std::uint64_t data_hash() const
Hash of the data this component draws from, for replay.
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).
static json_value default_to_json(const std::any &value, const std::wstring &display)
Default JSON conversion used by to_json().
component(const component &)=default
Copyable.
virtual std::vector< std::wstring > depends_on() const
Keys of the components this one reads from the context.
virtual std::wstring key() const =0
Unique key identifying this component (e.g. "name", "age").
component & operator=(const component &)=default
Copyable.
virtual json_value to_json(const std::any &value) const
Convert a generated value to JSON, for entity::to_json().
component(component &&) noexcept=default
Movable.
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.
std::vector< entity > generate_batch(const entity_request &request, const core::batch_options &options, std::uint64_t batch_seed) const
Generate a batch described by core::batch_options, deterministically.
std::vector< entity > generate_batch(const entity_request &request, std::uint64_t call_seed) const
Generate multiple entities using a seed.
std::vector< entity > generate_batch_async(const entity_request &request)
Generate multiple entities concurrently.
eg & clear_observers()
Remove all observers.
std::vector< entity > generate_batch_async(const entity_request &request, std::uint64_t call_seed) const
Generate multiple entities concurrently using a seed.
eg & weight(const std::wstring &component_key, double weight_value)
Set or update the weight override for a registered component.
entity generate(const entity_request &request, std::uint64_t call_seed) const
Generate an entity using a request and a seed.
entity generate_group(const entity_request &request)
Generate an entity using a named group.
eg & add_recipe_from_string(std::string_view text, std::string source="recipe")
Register the components a recipe in text declares.
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.
std::vector< entity > generate_batch(const entity_request &request, const core::batch_options &options)
Generate a batch described by core::batch_options, from a seed drawn from the generator's engine.
std::vector< std::wstring > component_keys() const
Return all registered component keys in registration order.
eg(eg &&) noexcept=default
Move constructor.
bool has_group(const std::wstring &group_name) const
Check if a named group exists.
eg & add(field_component< T > f)
Register a field built with field().
void for_each_entity(const entity_request &request, const core::batch_options &options, Sink &&sink)
Stream a batch of entities with a batch seed drawn from the generator's engine.
std::vector< entity > generate_batch(const entity_request &request)
Generate request.count entities using per-call options.
entity generate_group(const entity_request &request, std::uint64_t call_seed) const
Generate an entity using a named group with a seed.
eg(const eg &)=delete
Not copyable.
eg()=default
Default constructor creates an empty generator.
eg & add_recipe(const std::filesystem::path &path)
Register the components a recipe declares.
eg & add(std::unique_ptr< component > comp, double weight_override)
Register a component with a weight override.
eg & add_group(const std::wstring &group_name, std::vector< std::wstring > component_keys)
Register a named group of component keys.
void for_each_entity(const entity_request &request, const core::batch_options &options, std::uint64_t batch_seed, Sink &&sink) const
Stream a batch of entities to a sink without holding the batch.
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.
eg & operator=(const eg &)=delete
Not copyable.
entity generate(const entity_request &request)
Generate an entity using per-call options.
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.
core::seed_signature signature() const
The seed and the data this entity was generated from, in the format every dasmig result uses.
std::uint64_t seed(const std::wstring &component_key) const
Retrieve the random seed used to generate a specific component.
std::string to_json() const
This entity as a JSON document.
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.
void to_json(core::json_writer &w) const
Write this entity as a JSON object {"components":{"key":value,…},"signature":{…}},...
std::map< std::wstring, std::wstring > to_map() const
Return component values as a map of key to display string.
A component built from a callable, with every optional hook set by a chained call; see field().
field_component(std::wstring key, generator fn)
Construct a field.
field_component label(std::wstring text) &&
Set the display label (default: the key).
bool should_generate(const generation_context &ctx) const override
Decide whether this component should be generated.
field_component format(std::function< std::wstring(const T &)> fn) &&
Set how the value is shown by to_string().
field_component depends_on(std::vector< std::wstring > keys) &&
Declare the components this one reads; see component::depends_on().
std::function< T(const generation_context &)> generator
The callable that produces the value.
std::uint64_t data_hash() const override
Hash of the data this component draws from, for replay.
std::wstring to_string(const std::any &value) const override
Convert a generated value to a displayable string.
bool validate(const std::any &value) const override
Validate a generated value.
double weight() const override
Inclusion weight for this component (0.0 to 1.0).
field_component validate(std::function< bool(const T &)> fn) &&
Accept only values the callable accepts; see component::validate().
field_component weight(double inclusion) &&
Set the inclusion weight (0.0 to 1.0); see component::weight().
field_component data_hash(std::uint64_t hash) &&
Set the hash of the data the field draws from; see component::data_hash().
std::vector< std::wstring > depends_on() const override
Keys of the components this one reads from the context.
json_value to_json(const std::any &value) const override
Convert a generated value to JSON, for entity::to_json().
std::wstring label() const override
Display name of this component, used by entity::to_string().
std::any generate(const generation_context &ctx) const override
Generate a random value for this component.
std::wstring key() const override
Unique key identifying this component (e.g. "name", "age").
field_component when(std::function< bool(const generation_context &)> fn) &&
Generate the field only when the callable returns true; see component::should_generate().
field_component json(std::function< json_value(const T &)> fn) &&
Set how the value is written by to_json().
Context passed to components during generation.
bool has(const std::wstring &component_key) const
Check if a component value has already been generated.
T input_or(std::string_view name, T fallback) const
A per-call input, or a fallback when the request has none.
T get_or(const std::wstring &component_key, T fallback) const
Typed retrieval with a fallback.
T get(const std::wstring &component_key) const
Typed retrieval of an already-generated component value.
bool has_input(std::string_view name) const
Check whether the request supplied an input.
core::rng & random() const
Access the random engine for this component.
std::optional< T > try_get(const std::wstring &component_key) const
Typed retrieval that tolerates a missing value.
T input(std::string_view name) const
A per-call input from the request.
Observer interface for hooking into generation lifecycle events.
generation_observer(const generation_observer &)=default
Copyable.
generation_observer(generation_observer &&) noexcept=default
Movable.
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.
generation_observer()=default
Default constructor.
virtual void on_before_entity_retry(std::size_t attempt)
Called before an entity validation retry.
generation_observer & operator=(const generation_observer &)=default
Copyable.
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.
A component value as JSON: null, a boolean, an integer, a number, a (UTF-8) string,...
object * get_object()
The object held, or null when this is not an object.
std::vector< json_value > array
A JSON array.
void write(core::json_writer &w) const
Write this value.
const object * get_object() const
The object held, or null when this is not an object.
json_value(B b)
A boolean. Only bool itself: a pointer (such as a wide literal L"...") would otherwise convert and wr...
std::vector< std::pair< std::string, json_value > > object
A JSON object, members in order.
json_value(std::nullptr_t)
null. // NOLINT(google-explicit-constructor)
json_value(F d)
A number; a non-finite one is null.
const array * get_array() const
The array held, or null when this is not an array.
json_value(std::string s)
A string (UTF-8). // NOLINT(google-explicit-constructor)
json_value(const char *s)
A string (UTF-8). // NOLINT(google-explicit-constructor)
json_value()=default
null.
json_value(I n)
An integer.
json_value(object o)
An object. // NOLINT(google-explicit-constructor)
json_value(array a)
An array. // NOLINT(google-explicit-constructor)
Component that generates a uniform random value: integers in [lo, hi], floating-point values in [lo,...
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").
auto field(std::wstring key, Fn fn)
Build a field from a callable; T is what the callable returns.
std::map< std::string, input_value, std::less<> > input_map
Per-call inputs by name (ordered, so their hash is reproducible).
std::variant< bool, std::int64_t, double, std::string > input_value
A per-call input value: a boolean, an integer, a number or a string.
Per-call entity generation and selection options.
input_map inputs
Per-call inputs for the components (a country, a reference date, a forced sex, ......
std::function< bool(const entity &)> validator
Optional entity validator; false retries the whole entity. Concurrent batches require a thread-safe c...
std::optional< std::vector< std::wstring > > component_keys
Optional key subset; absent selects all, an empty vector selects none. Unknown keys are skipped and r...
std::size_t count
Number of entities for batch methods (ignored by single-entity methods).
std::optional< std::wstring > group
Optional registered group name, mutually exclusive with component_keys. Required by eg::generate_grou...
A value–weight pair for weighted selection.
T value
The selectable value.
double weight
Relative selection weight (must be >= 0).