Entity Generator 1.1.0
Composable, deterministic entity generation for C++23
Loading...
Searching...
No Matches
entitygen.hpp
Go to the documentation of this file.
1#ifndef DASMIG_ENTITYGEN_HPP
2#define DASMIG_ENTITYGEN_HPP
3
4#include "random.hpp"
5#include <algorithm>
6#include <any>
7#include <cstdint>
8#include <functional>
9#include <future>
10#include <iterator>
11#include <map>
12#include <memory>
13#include <optional>
14#include <ostream>
15#include <ranges>
16#include <span>
17#include <stdexcept>
18#include <string>
19#include <type_traits>
20#include <vector>
21
22/// @file entitygen.hpp
23/// @brief Entity generator library — composable, deterministic entity generation for C++23.
24/// @author Diego Dasso Migotto (diegomigotto at hotmail dot com)
25/// @see See doc/usage.md for the narrative tutorial.
26namespace dasmig
27{
28
29// Forward declaration.
30class eg;
31
32/// @brief Context passed to components during generation.
33///
34/// Provides access to previously generated component values and a seeded
35/// random engine. Created and populated by the entity generator; not
36/// user-constructible.
37/// @see component::generate()
39{
40 public:
41 /// @brief Check if a component value has already been generated.
42 /// @param component_key The component key to look up.
43 /// @return `true` if a value for the key exists in the context.
44 [[nodiscard]] bool has(const std::wstring& component_key) const
45 {
46 return _values.contains(component_key);
47 }
48
49 /// @brief Typed retrieval of an already-generated component value.
50 /// @tparam T The expected type of the stored value.
51 /// @param component_key The component key to retrieve.
52 /// @return The value cast to @p T.
53 /// @throws std::bad_any_cast If @p T does not match the stored type.
54 template <typename T>
55 [[nodiscard]] T get(const std::wstring& component_key) const
56 {
57 return std::any_cast<T>(_values.at(component_key));
58 }
59
60 /// @brief Access the random engine for this generation.
61 /// @return A reference to the per-component seeded random engine.
62 [[nodiscard]] effolkronium::random_local& random() const
63 {
64 return _random;
65 }
66
67 private:
68 // Private constructor, this is managed by the entity generator.
69 generation_context() = default;
70
71 // Already-generated component values indexed by key.
72 std::map<std::wstring, std::any> _values;
73
74 // Instance-based random engine for reproducible generation.
75 mutable effolkronium::random_local _random;
76
77 // Allows entity generator to construct and populate contexts.
78 friend class eg;
79};
80
81/// @brief Abstract base for entity components.
82///
83/// Implement this interface to define custom components that the entity
84/// generator can produce. Each component has a unique key, generates typed
85/// random values, and converts them to display strings.
86/// @see generation_context, eg::add()
88{
89 public:
90 /// @brief Virtual destructor for proper cleanup of derived classes.
91 virtual ~component() = default;
92
93 /// @brief Unique key identifying this component (e.g. `"name"`, `"age"`).
94 /// @return The component key as a wide string.
95 [[nodiscard]] virtual std::wstring key() const = 0;
96
97 /// @brief Generate a random value for this component.
98 /// @param ctx Context providing access to previously generated values
99 /// and a seeded random engine.
100 /// @return The generated value wrapped in `std::any`.
101 [[nodiscard]] virtual std::any generate(
102 const generation_context& ctx) const = 0;
103
104 /// @brief Convert a generated value to a displayable string.
105 /// @param value The value previously returned by generate().
106 /// @return A human-readable wide-string representation.
107 /// @see default_to_string()
108 [[nodiscard]] virtual std::wstring to_string(const std::any& value) const = 0;
109
110 /// @brief Inclusion weight for this component (0.0 to 1.0).
111 ///
112 /// A value of 1.0 means always included; 0.5 means included roughly
113 /// half the time. The generator may override this per-component at
114 /// registration time via eg::add(comp, weight) or eg::weight().
115 /// @return The inclusion probability.
116 /// @see eg::weight()
117 [[nodiscard]] virtual double weight() const { return 1.0; }
118
119 /// @brief Validate a generated value.
120 /// @param value The value to validate.
121 /// @return `true` to accept, `false` to retry generation.
122 /// @see eg::max_retries()
123 [[nodiscard]] virtual bool validate([[maybe_unused]] const std::any& value) const
124 {
125 return true;
126 }
127
128 /// @brief Decide whether this component should be generated.
129 ///
130 /// Unlike weight() (probabilistic), this is logic-driven. Return
131 /// `false` to skip generation entirely based on the current context.
132 /// @param ctx Context with previously generated component values.
133 /// @return `true` to generate, `false` to skip.
134 [[nodiscard]] virtual bool should_generate(
135 [[maybe_unused]] const generation_context& ctx) const
136 {
137 return true;
138 }
139
140 protected:
141 /// @brief Default conversion covering common standard types.
142 ///
143 /// Handles `std::wstring`, `int`, `double`, `float`, `long`, and `bool`.
144 /// Returns `"[?]"` for unrecognized types.
145 /// @param value The value to convert.
146 /// @return A wide-string representation.
147 [[nodiscard]] static std::wstring default_to_string(const std::any& value)
148 {
149 if (value.type() == typeid(std::wstring))
150 {
151 return std::any_cast<std::wstring>(value);
152 }
153 if (value.type() == typeid(int))
154 {
155 return std::to_wstring(std::any_cast<int>(value));
156 }
157 if (value.type() == typeid(double))
158 {
159 return std::to_wstring(std::any_cast<double>(value));
160 }
161 if (value.type() == typeid(float))
162 {
163 return std::to_wstring(std::any_cast<float>(value));
164 }
165 if (value.type() == typeid(long))
166 {
167 return std::to_wstring(std::any_cast<long>(value));
168 }
169 if (value.type() == typeid(bool))
170 {
171 return std::any_cast<bool>(value) ? L"true" : L"false";
172 }
173
174 return L"[?]";
175 }
176};
177
178/// @name Generic Components
179/// @brief Reusable component templates for common patterns.
180/// @{
181
182/// @brief Tag type indicating that generic components should use default_to_string().
184
185/// @brief Component that always returns a fixed value.
186/// @tparam T The value type.
187/// @tparam Formatter Optional callable for custom string conversion
188/// (defaults to use_default_formatter).
189template <typename T, typename Formatter = use_default_formatter>
191{
192 public:
193 /// @brief Construct a constant component.
194 /// @param key Unique component key.
195 /// @param value The fixed value to always return.
196 /// @param fmt Optional custom formatter.
197 explicit constant_component(std::wstring key, T value,
198 Formatter fmt = {})
199 : _key{std::move(key)}, _value{std::move(value)},
200 _fmt{std::move(fmt)} {}
201
202 [[nodiscard]] std::wstring key() const override { return _key; }
203
204 [[nodiscard]] std::any generate(
205 const generation_context& /*ctx*/) const override
206 {
207 return _value;
208 }
209
210 [[nodiscard]] std::wstring to_string(const std::any& value) const override
211 {
212 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
213 return default_to_string(value);
214 else
215 return _fmt(std::any_cast<T>(value));
216 }
217
218 private:
219 std::wstring _key;
220 T _value;
221 Formatter _fmt;
222};
223
224/// @brief Component that picks uniformly at random from a list of values.
225/// @tparam T The value type.
226/// @tparam Formatter Optional callable for custom string conversion.
227template <typename T, typename Formatter = use_default_formatter>
229{
230 public:
231 /// @brief Construct a choice component.
232 /// @param key Unique component key.
233 /// @param choices Non-empty list of values to choose from.
234 /// @param fmt Optional custom formatter.
235 /// @throws std::invalid_argument If @p choices is empty.
236 explicit choice_component(std::wstring key, std::vector<T> choices,
237 Formatter fmt = {})
238 : _key{std::move(key)}, _choices{std::move(choices)},
239 _fmt{std::move(fmt)}
240 {
241 if (_choices.empty())
242 throw std::invalid_argument("choices must not be empty");
243 }
244
245 [[nodiscard]] std::wstring key() const override { return _key; }
246
247 [[nodiscard]] std::any generate(
248 const generation_context& ctx) const override
249 {
250 return *ctx.random().get(_choices);
251 }
252
253 [[nodiscard]] std::wstring to_string(const std::any& value) const override
254 {
255 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
256 return default_to_string(value);
257 else
258 return _fmt(std::any_cast<T>(value));
259 }
260
261 private:
262 std::wstring _key;
263 std::vector<T> _choices;
264 Formatter _fmt;
265};
266
267/// @brief Component that generates a uniform random value in [lo, hi].
268/// @tparam T An arithmetic type (`int`, `double`, `float`, etc.).
269/// @tparam Formatter Optional callable for custom string conversion.
270template <typename T, typename Formatter = use_default_formatter>
271 requires std::is_arithmetic_v<T>
273{
274 public:
275 /// @brief Construct a range component.
276 /// @param key Unique component key.
277 /// @param lo Lower bound (inclusive).
278 /// @param hi Upper bound (inclusive).
279 /// @param fmt Optional custom formatter.
280 explicit range_component(std::wstring key, T lo, T hi,
281 Formatter fmt = {})
282 : _key{std::move(key)}, _lo{lo}, _hi{hi}, _fmt{std::move(fmt)} {}
283
284 [[nodiscard]] std::wstring key() const override { return _key; }
285
286 [[nodiscard]] std::any generate(
287 const generation_context& ctx) const override
288 {
289 return ctx.random().get(_lo, _hi);
290 }
291
292 [[nodiscard]] std::wstring to_string(const std::any& value) const override
293 {
294 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
295 return default_to_string(value);
296 else
297 return _fmt(std::any_cast<T>(value));
298 }
299
300 private:
301 std::wstring _key;
302 T _lo;
303 T _hi;
304 Formatter _fmt;
305};
306
307/// @brief Component that wraps a callable for one-off or computed values.
308///
309/// Avoids subclassing component entirely. The callable receives a
310/// generation_context and returns a value of type @p T.
311/// @tparam T The return type of the callable.
312/// @tparam GenFn Callable type taking `const generation_context&`.
313/// @tparam Formatter Optional callable for custom string conversion.
314template <typename T, typename GenFn, typename Formatter = use_default_formatter>
316{
317 public:
318 /// @brief Construct a callback component.
319 /// @param key Unique component key.
320 /// @param fn Callable invoked during generation.
321 /// @param fmt Optional custom formatter.
322 explicit callback_component(std::wstring key, GenFn fn,
323 Formatter fmt = {})
324 : _key{std::move(key)}, _fn{std::move(fn)}, _fmt{std::move(fmt)} {}
325
326 [[nodiscard]] std::wstring key() const override { return _key; }
327
328 [[nodiscard]] std::any generate(
329 const generation_context& ctx) const override
330 {
331 return _fn(ctx);
332 }
333
334 [[nodiscard]] std::wstring to_string(const std::any& value) const override
335 {
336 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
337 return default_to_string(value);
338 else
339 return _fmt(std::any_cast<T>(value));
340 }
341
342 private:
343 std::wstring _key;
344 GenFn _fn;
345 Formatter _fmt;
346};
347
348/// @brief Deduction guide for callback_component.
349///
350/// Deduces @p T from the return type of @p GenFn so users don't need
351/// to spell out the lambda type explicitly.
352template <typename GenFn, typename Formatter = use_default_formatter>
353callback_component(std::wstring, GenFn, Formatter = {})
354 -> callback_component<
355 std::invoke_result_t<GenFn, const generation_context&>,
356 GenFn, Formatter>;
357
358/// @brief Component that picks from a list of values using per-option weights.
359///
360/// Weights are relative (they don't need to sum to 1.0). A weight of 0
361/// means the option is never selected.
362/// @tparam T The value type.
363/// @tparam Formatter Optional callable for custom string conversion.
364template <typename T, typename Formatter = use_default_formatter>
366{
367 public:
368 /// @brief A value–weight pair for weighted selection.
369 struct option
370 {
371 T value; ///< The selectable value.
372 double weight; ///< Relative selection weight (must be >= 0).
373 };
374
375 /// @brief Construct a weighted choice component.
376 /// @param key Unique component key.
377 /// @param options Non-empty list of value–weight pairs. At least one
378 /// must have a positive weight.
379 /// @param fmt Optional custom formatter.
380 /// @throws std::invalid_argument If @p options is empty or all weights are zero.
381 explicit weighted_choice_component(std::wstring key,
382 std::vector<option> options,
383 Formatter fmt = {})
384 : _key{std::move(key)}, _options{std::move(options)},
385 _fmt{std::move(fmt)}
386 {
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");
393 }
394
395 [[nodiscard]] std::wstring key() const override { return _key; }
396
397 [[nodiscard]] std::any generate(
398 const generation_context& ctx) const override
399 {
400 std::vector<double> weights;
401 weights.reserve(_options.size());
402 std::ranges::transform(_options, std::back_inserter(weights),
404
405 std::discrete_distribution<std::size_t> dist(
406 weights.begin(), weights.end());
407 return _options[dist(ctx.random().engine())].value;
408 }
409
410 [[nodiscard]] std::wstring to_string(const std::any& value) const override
411 {
412 if constexpr (std::is_same_v<Formatter, use_default_formatter>)
413 return default_to_string(value);
414 else
415 return _fmt(std::any_cast<T>(value));
416 }
417
418 private:
419 std::wstring _key;
420 std::vector<option> _options;
421 Formatter _fmt;
422};
423
424/// @}
425
426/// @brief A generated entity holding component values in registration order.
427///
428/// Entities are produced by eg::generate() and provide typed access to
429/// component values, seed signatures for replay, and serialization.
430/// @see eg::generate()
432{
433 public:
434 /// @brief Typed retrieval of a component value by key.
435 /// @tparam T The expected type of the stored value.
436 /// @param component_key The component key to retrieve.
437 /// @return The value cast to @p T.
438 /// @throws std::out_of_range If the key is not present.
439 /// @throws std::bad_any_cast If @p T does not match the stored type.
440 template <typename T>
441 [[nodiscard]] T get(const std::wstring& component_key) const
442 {
443 return std::any_cast<T>(find_value(component_key));
444 }
445
446 /// @brief Type-erased retrieval of a component value by key.
447 /// @param component_key The component key to retrieve.
448 /// @return A const reference to the stored `std::any`.
449 /// @throws std::out_of_range If the key is not present.
450 [[nodiscard]] const std::any& get_any(const std::wstring& component_key) const
451 {
452 return find_value(component_key);
453 }
454
455 /// @brief Check if a component value exists by key.
456 /// @param component_key The component key to look up.
457 /// @return `true` if the entity contains a value for the key.
458 [[nodiscard]] bool has(const std::wstring& component_key) const
459 {
460 return std::ranges::any_of(_entries, [&component_key](const auto& e) {
461 return e.key == component_key;
462 });
463 }
464
465 /// @brief Retrieve the random seed used to generate a specific component.
466 /// @param component_key The component key.
467 /// @return The per-component seed for replay.
468 /// @throws std::out_of_range If the key is not present.
469 [[nodiscard]] std::uint64_t seed(const std::wstring& component_key) const
470 {
471 auto it = std::ranges::find(_entries, component_key, &entry::key);
472
473 if (it == _entries.end())
474 {
475 throw std::out_of_range("component not found");
476 }
477
478 return it->seed;
479 }
480
481 /// @brief Retrieve the random seed used to generate this entity.
482 ///
483 /// This seed can reproduce all component seeds and thus the entire entity.
484 /// @return The entity-level seed.
485 [[nodiscard]] std::uint64_t seed() const
486 {
487 return _seed;
488 }
489
490 /// @brief Get all component keys present in this entity, in generation order.
491 /// @return A vector of component keys.
492 [[nodiscard]] std::vector<std::wstring> keys() const
493 {
494 std::vector<std::wstring> result;
495 result.reserve(_entries.size());
496 std::ranges::transform(_entries, std::back_inserter(result), &entry::key);
497 return result;
498 }
499
500 /// @brief Return component values as a map of key to display string.
501 /// @return An ordered map of key → formatted value.
502 [[nodiscard]] std::map<std::wstring, std::wstring> to_map() const
503 {
504 std::map<std::wstring, std::wstring> result;
505 for (const auto& e : _entries)
506 {
507 result.emplace(e.key, e.display);
508 }
509 return result;
510 }
511
512 /// @brief Number of component values in this entity.
513 [[nodiscard]] std::size_t size() const { return _entries.size(); }
514
515 /// @brief Check if the entity has no component values.
516 [[nodiscard]] bool empty() const { return _entries.empty(); }
517
518 /// @brief Convert all component values to a single formatted string.
519 ///
520 /// Each entry is rendered as `"key: display"` separated by two spaces.
521 /// @return The formatted string, or empty if the entity is empty.
522 [[nodiscard]] std::wstring to_string() const
523 {
524 auto parts = _entries
525 | std::views::transform([](const auto& e) {
526 return e.key + L": " + e.display;
527 });
528
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{});
533 }
534
535 /// @brief Stream all component values in generation order.
536 friend std::wostream& operator<<(std::wostream& wos, const entity& e)
537 {
538 return wos << e.to_string();
539 }
540
541 private:
542 // A single component entry in generation order.
543 struct entry
544 {
545 std::wstring key;
546 std::any value;
547 std::wstring display;
548 std::uint64_t seed;
549 };
550
551 // Private constructor, this is mostly a helper class to the entity
552 // generator, not the intended API.
553 entity() = default;
554
555 // Find a component value by key.
556 [[nodiscard]] const std::any& find_value(const std::wstring& component_key) const
557 {
558 auto it = std::ranges::find(_entries, component_key, &entry::key);
559
560 if (it == _entries.end())
561 {
562 throw std::out_of_range("component not found");
563 }
564
565 return it->value;
566 }
567
568 // Component entries in generation order.
569 std::vector<entry> _entries;
570
571 // Seed used to generate this entity.
572 std::uint64_t _seed{0};
573
574 // Allows entity generator to construct entities.
575 friend class eg;
576};
577
578/// @brief Observer interface for hooking into generation lifecycle events.
579///
580/// Override only the methods you care about; all default to no-ops.
581/// Multiple observers can be attached to a generator via eg::add_observer().
582/// @see eg::add_observer(), dasmig::ext::stats_observer
584{
585 public:
586 /// @brief Virtual destructor.
587 virtual ~generation_observer() = default;
588
589 /// @name Entity Lifecycle
590 /// @{
591
592 /// @brief Called before an entity is generated.
593 virtual void on_before_generate() {}
594 /// @brief Called after an entity is successfully generated.
595 /// @param e The generated entity.
596 virtual void on_after_generate([[maybe_unused]] const entity& e) {}
597
598 /// @}
599 /// @name Component Generation
600 /// @{
601
602 /// @brief Called before a component is generated.
603 /// @param key The component key about to be generated.
604 virtual void on_before_component([[maybe_unused]] const std::wstring& key) {}
605 /// @brief Called after a component is successfully generated.
606 /// @param key The component key.
607 /// @param value The generated value.
608 virtual void on_after_component([[maybe_unused]] const std::wstring& key,
609 [[maybe_unused]] const std::any& value) {}
610
611 /// @}
612 /// @name Component Skip
613 /// @{
614
615 /// @brief Called when a component is skipped (weight roll or conditional exclusion).
616 /// @param key The skipped component key.
617 virtual void on_skip([[maybe_unused]] const std::wstring& key) {}
618
619 /// @}
620 /// @name Component Validation
621 /// @{
622
623 /// @brief Called before a component validation retry.
624 /// @param key The component key being retried.
625 /// @param attempt The retry attempt number (1-based).
626 virtual void on_before_retry([[maybe_unused]] const std::wstring& key,
627 [[maybe_unused]] std::size_t attempt) {}
628 /// @brief Called after a component validation retry.
629 /// @param key The component key.
630 /// @param attempt The retry attempt number.
631 /// @param value The newly generated value.
632 virtual void on_after_retry([[maybe_unused]] const std::wstring& key,
633 [[maybe_unused]] std::size_t attempt,
634 [[maybe_unused]] const std::any& value) {}
635
636 /// @brief Called when component validation is exhausted (precedes exception).
637 /// @param key The failed component key.
638 virtual void on_component_fail([[maybe_unused]] const std::wstring& key) {}
639
640 /// @}
641 /// @name Entity Validation
642 /// @{
643
644 /// @brief Called before an entity validation retry.
645 /// @param attempt The entity retry attempt number (1-based).
646 virtual void on_before_entity_retry([[maybe_unused]] std::size_t attempt) {}
647 /// @brief Called after an entity validation retry.
648 /// @param attempt The entity retry attempt number.
649 virtual void on_after_entity_retry([[maybe_unused]] std::size_t attempt) {}
650
651 /// @brief Called when entity validation is exhausted (precedes exception).
652 virtual void on_entity_fail() {}
653
654 /// @}
655 /// @name Component Registration
656 /// @{
657
658 /// @brief Called before a component is registered.
659 /// @param key The component key being added.
660 virtual void on_before_add([[maybe_unused]] const std::wstring& key) {}
661 /// @brief Called after a component is registered.
662 /// @param key The component key that was added.
663 virtual void on_after_add([[maybe_unused]] const std::wstring& key) {}
664
665 /// @brief Called before a component is removed.
666 /// @param key The component key being removed.
667 virtual void on_before_remove([[maybe_unused]] const std::wstring& key) {}
668 /// @brief Called after a component is removed.
669 /// @param key The component key that was removed.
670 virtual void on_after_remove([[maybe_unused]] const std::wstring& key) {}
671
672 /// @}
673};
674
675/// @brief The entity generator — produces entities with configurable components.
676///
677/// Components are registered by implementing the component interface and
678/// adding them to the generator. Components are generated in registration
679/// order, allowing later components to access earlier ones via context.
680/// @see component, entity, generation_context
681class eg
682{
683 public:
684 /// @brief Default constructor creates an empty generator.
685 eg() = default;
686
687 eg(const eg&) = delete; ///< Not copyable.
688 eg& operator=(const eg&) = delete; ///< Not copyable.
689 eg(eg&&) = default; ///< Move constructor.
690 eg& operator=(eg&&) = default; ///< Move assignment.
691 ~eg() = default; ///< Default destructor.
692
693 /// @brief Access the global singleton instance.
694 ///
695 /// For multi-threaded use, prefer creating independent eg instances.
696 /// @return A reference to the singleton.
697 static eg& instance()
698 {
699 static eg instance;
700 return instance;
701 }
702
703 /// @name Registration
704 /// @{
705
706 /// @brief Register a component.
707 ///
708 /// Takes ownership of the component. If a component with the same key
709 /// already exists, it will be replaced. Components are generated in the
710 /// order they are registered.
711 /// @param comp The component to register (moved).
712 /// @return `*this` for chaining.
713 eg& add(std::unique_ptr<component> comp)
714 {
715 const auto key = comp->key();
717
718 auto it = std::ranges::find(_components, key, &component_entry::key);
719
720 if (it != _components.end())
721 {
722 it->comp = std::move(comp);
723 }
724 else
725 {
726 _components.push_back({key, std::move(comp)});
727 }
728
730 return *this;
731 }
732
733 /// @brief Register a component with a weight override.
734 ///
735 /// The override takes precedence over the component's own weight() method.
736 /// @param comp The component to register (moved).
737 /// @param weight_override Inclusion probability override (0.0–1.0).
738 /// @return `*this` for chaining.
739 eg& add(std::unique_ptr<component> comp, double weight_override)
740 {
741 const auto key = comp->key();
742 add(std::move(comp));
743 _weight_overrides[key] = weight_override;
744 return *this;
745 }
746
747 /// @brief Set or update the weight override for a registered component.
748 /// @param component_key The component key.
749 /// @param weight_value New inclusion probability (0.0–1.0).
750 /// @return `*this` for chaining.
751 /// @throws std::out_of_range If the component is not registered.
752 eg& weight(const std::wstring& component_key, double weight_value)
753 {
754 if (!has(component_key))
755 {
756 throw std::out_of_range("component not found");
757 }
758
759 _weight_overrides[component_key] = weight_value;
760 return *this;
761 }
762
763 /// @brief Remove a registered component by key.
764 /// @param component_key The component key to remove.
765 /// @return `*this` for chaining.
766 eg& remove(const std::wstring& component_key)
767 {
768 notify(&generation_observer::on_before_remove, component_key);
769
770 std::erase_if(_components, [&component_key](const auto& entry) {
771 return entry.key == component_key;
772 });
773 _weight_overrides.erase(component_key);
774
775 notify(&generation_observer::on_after_remove, component_key);
776 return *this;
777 }
778
779 /// @brief Check if a component is registered by key.
780 /// @param component_key The component key to look up.
781 /// @return `true` if a component with the key exists.
782 [[nodiscard]] bool has(const std::wstring& component_key) const
783 {
784 return std::ranges::any_of(_components, [&component_key](const auto& entry) {
785 return entry.key == component_key;
786 });
787 }
788
789 /// @brief Remove all registered components, weight overrides, and groups.
790 /// @return `*this` for chaining.
792 {
793 _components.clear();
794 _weight_overrides.clear();
795 _groups.clear();
796 return *this;
797 }
798
799 /// @brief Return the number of registered components.
800 [[nodiscard]] std::size_t size() const { return _components.size(); }
801
802 /// @brief Return all registered component keys in registration order.
803 /// @return A vector of component keys.
804 [[nodiscard]] std::vector<std::wstring> component_keys() const
805 {
806 std::vector<std::wstring> keys;
807 keys.reserve(_components.size());
808 std::ranges::transform(_components, std::back_inserter(keys),
809 &component_entry::key);
810 return keys;
811 }
812
813 /// @}
814 /// @name Seeding
815 /// @{
816
817 /// @brief Seed the internal random engine.
818 ///
819 /// Subsequent generate() calls without an explicit seed will draw from
820 /// this seeded sequence.
821 /// @param seed_value The seed value.
822 /// @return `*this` for chaining.
823 eg& seed(std::uint64_t seed_value)
824 {
825 _engine.seed(static_cast<std::mt19937::result_type>(seed_value));
826
827 return *this;
828 }
829
830 /// @brief Reseed the engine with a non-deterministic source.
831 ///
832 /// Subsequent generate() calls will produce non-reproducible results.
833 /// @return `*this` for chaining.
835 {
836 _engine.seed(std::random_device{}());
837
838 return *this;
839 }
840
841 /// @}
842 /// @name Validation
843 /// @{
844
845 /// @brief Set an entity-level validation function.
846 ///
847 /// After each entity is fully generated, the validator is called. If it
848 /// returns `false`, the entity is re-generated up to max_retries times.
849 /// @param fn Validator callable returning `true` to accept.
850 /// @return `*this` for chaining.
851 eg& set_validator(std::function<bool(const entity&)> fn)
852 {
853 _validator = std::move(fn);
854 return *this;
855 }
856
857 /// @brief Remove the entity-level validation function.
858 /// @return `*this` for chaining.
860 {
861 _validator = nullptr;
862 return *this;
863 }
864
865 /// @brief Set the maximum number of retries for validation (default 10).
866 ///
867 /// Applies to both component-level and entity-level validation.
868 /// @param retries The maximum retry count.
869 /// @return `*this` for chaining.
870 eg& max_retries(std::size_t retries)
871 {
872 _max_retries = retries;
873 return *this;
874 }
875
876 /// @}
877 /// @name Observers
878 /// @{
879
880 /// @brief Add an observer to receive generation lifecycle callbacks.
881 /// @param obs The observer (shared ownership).
882 /// @return `*this` for chaining.
883 /// @see generation_observer
884 eg& add_observer(std::shared_ptr<generation_observer> obs)
885 {
886 _observers.push_back(std::move(obs));
887 return *this;
888 }
889
890 /// @brief Remove a specific observer by identity.
891 /// @param obs The observer to remove.
892 /// @return `*this` for chaining.
893 eg& remove_observer(const std::shared_ptr<generation_observer>& obs)
894 {
895 std::erase(_observers, obs);
896 return *this;
897 }
898
899 /// @brief Remove all observers.
900 /// @return `*this` for chaining.
902 {
903 _observers.clear();
904 return *this;
905 }
906
907 /// @}
908 /// @name Generation
909 /// @{
910
911 /// @brief Generate an entity with all registered components.
912 ///
913 /// Uses the generator's internal engine.
914 /// @return The generated entity.
915 /// @throws std::runtime_error If component or entity validation is exhausted.
916 [[nodiscard]] entity generate()
917 {
918 auto refs = all_component_refs();
919 return generate_with_hooks([&] {
920 return generate_impl(refs, _engine, _max_retries, _observers);
921 });
922 }
923
924 /// @brief Generate an entity with all registered components using a seed.
925 /// @param call_seed Seed for reproducible results.
926 /// @return The generated entity.
927 /// @throws std::runtime_error If validation is exhausted.
928 [[nodiscard]] entity generate(std::uint64_t call_seed) const
929 {
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);
934 });
935 }
936
937 /// @brief Generate an entity with only the specified components.
938 ///
939 /// Uses the generator's internal engine. Unknown keys are silently skipped.
940 /// @param component_keys The keys to include.
941 /// @return The generated entity.
942 /// @throws std::runtime_error If validation is exhausted.
943 [[nodiscard]] entity generate(std::span<const std::wstring> component_keys)
944 {
945 auto filtered = filter_components(component_keys);
946 return generate_with_hooks([&] {
947 return generate_impl(filtered, _engine, _max_retries, _observers);
948 });
949 }
950
951 /// @brief Generate an entity with only the specified components using a seed.
952 /// @param component_keys The keys to include.
953 /// @param call_seed Seed for reproducible results.
954 /// @return The generated entity.
955 /// @throws std::runtime_error If validation is exhausted.
956 [[nodiscard]] entity generate(std::span<const std::wstring> component_keys,
957 std::uint64_t call_seed) const
958 {
959 auto filtered = filter_components(component_keys);
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);
963 });
964 }
965
966 /// @brief Generate with an initializer list of keys (convenience overload).
967 [[nodiscard]] entity generate(
968 std::initializer_list<std::wstring> component_keys)
969 {
970 return generate(std::span<const std::wstring>{
971 component_keys.begin(), component_keys.size()});
972 }
973
974 /// @brief Generate with an initializer list of keys and a seed.
975 [[nodiscard]] entity generate(
976 std::initializer_list<std::wstring> component_keys,
977 std::uint64_t call_seed) const
978 {
979 return generate(std::span<const std::wstring>{
980 component_keys.begin(), component_keys.size()}, call_seed);
981 }
982
983 /// @}
984 /// @name Batch Generation
985 /// @{
986
987 /// @brief Generate multiple entities with all registered components.
988 /// @param count Number of entities to generate.
989 /// @return A vector of generated entities.
990 [[nodiscard]] std::vector<entity> generate_batch(std::size_t count)
991 {
992 std::vector<entity> entities;
993 entities.reserve(count);
994
995 auto refs = all_component_refs();
996 std::generate_n(std::back_inserter(entities), count,
997 [&] {
998 return generate_with_hooks([&] {
999 return generate_impl(refs, _engine, _max_retries, _observers);
1000 });
1001 });
1002
1003 return entities;
1004 }
1005
1006 /// @brief Generate multiple entities using a seed.
1007 ///
1008 /// The seed initializes the engine once; successive entities draw from
1009 /// the same deterministic sequence.
1010 /// @param count Number of entities to generate.
1011 /// @param call_seed Seed for reproducible results.
1012 /// @return A vector of generated entities.
1013 [[nodiscard]] std::vector<entity> generate_batch(
1014 std::size_t count, std::uint64_t call_seed) const
1015 {
1016 std::vector<entity> entities;
1017 entities.reserve(count);
1018
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,
1022 [&] {
1023 return generate_with_hooks([&] {
1024 return generate_impl(refs, engine, _max_retries, _observers);
1025 });
1026 });
1027
1028 return entities;
1029 }
1030
1031 /// @}
1032 /// @name Concurrent Batch Generation
1033 /// @{
1034
1035 /// @brief Generate multiple entities concurrently.
1036 ///
1037 /// Entity seeds are pre-derived sequentially from the engine, then each
1038 /// entity is generated in its own async task. Results are returned in
1039 /// seed order. If observers are set, the caller is responsible for their
1040 /// thread safety.
1041 /// @param count Number of entities to generate.
1042 /// @return A vector of generated entities.
1043 [[nodiscard]] std::vector<entity> generate_batch_async(std::size_t count)
1044 {
1045 auto refs = all_component_refs();
1046
1047 std::vector<std::uint64_t> seeds(count);
1048 std::ranges::generate(seeds,
1049 [&] { return static_cast<std::uint64_t>(_engine()); });
1050
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);
1062 });
1063 });
1064 });
1065
1066 std::vector<entity> entities;
1067 entities.reserve(count);
1068 std::ranges::transform(futures, std::back_inserter(entities),
1069 [](auto& f) { return f.get(); });
1070
1071 return entities;
1072 }
1073
1074 /// @brief Generate multiple entities concurrently using a seed.
1075 ///
1076 /// The seed initializes a dedicated engine for pre-deriving per-entity
1077 /// seeds; all subsequent generation is parallel.
1078 /// @param count Number of entities to generate.
1079 /// @param call_seed Seed for reproducible results.
1080 /// @return A vector of generated entities.
1081 [[nodiscard]] std::vector<entity> generate_batch_async(
1082 std::size_t count, std::uint64_t call_seed) const
1083 {
1084 auto refs = all_component_refs();
1085
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()); });
1091
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);
1103 });
1104 });
1105 });
1106
1107 std::vector<entity> entities;
1108 entities.reserve(count);
1109 std::ranges::transform(futures, std::back_inserter(entities),
1110 [](auto& f) { return f.get(); });
1111
1112 return entities;
1113 }
1114
1115 /// @}
1116 /// @name Component Groups
1117 /// @{
1118
1119 /// @brief Register a named group of component keys.
1120 /// @param group_name Name for the group.
1121 /// @param component_keys Keys in the group.
1122 /// @return `*this` for chaining.
1123 eg& add_group(const std::wstring& group_name,
1124 std::vector<std::wstring> component_keys)
1125 {
1126 _groups[group_name] = std::move(component_keys);
1127 return *this;
1128 }
1129
1130 /// @brief Remove a named group.
1131 /// @param group_name The group to remove.
1132 /// @return `*this` for chaining.
1133 eg& remove_group(const std::wstring& group_name)
1134 {
1135 _groups.erase(group_name);
1136 return *this;
1137 }
1138
1139 /// @brief Check if a named group exists.
1140 /// @param group_name The group name to look up.
1141 /// @return `true` if the group exists.
1142 [[nodiscard]] bool has_group(const std::wstring& group_name) const
1143 {
1144 return _groups.contains(group_name);
1145 }
1146
1147 /// @brief Generate an entity using a named group.
1148 /// @param group_name The group to generate.
1149 /// @return The generated entity.
1150 /// @throws std::out_of_range If the group does not exist.
1151 [[nodiscard]] entity generate_group(const std::wstring& group_name)
1152 {
1153 auto it = _groups.find(group_name);
1154 if (it == _groups.end())
1155 {
1156 throw std::out_of_range("group not found");
1157 }
1158
1159 auto filtered = filter_components(it->second);
1160 return generate_with_hooks([&] {
1161 return generate_impl(filtered, _engine, _max_retries, _observers);
1162 });
1163 }
1164
1165 /// @brief Generate an entity using a named group with a seed.
1166 /// @param group_name The group to generate.
1167 /// @param call_seed Seed for reproducible results.
1168 /// @return The generated entity.
1169 /// @throws std::out_of_range If the group does not exist.
1170 [[nodiscard]] entity generate_group(const std::wstring& group_name,
1171 std::uint64_t call_seed) const
1172 {
1173 auto it = _groups.find(group_name);
1174 if (it == _groups.end())
1175 {
1176 throw std::out_of_range("group not found");
1177 }
1178
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);
1183 });
1184 }
1185
1186 /// @}
1187
1188 private:
1189 // Component entry: key + owned component pointer.
1190 struct component_entry
1191 {
1192 std::wstring key;
1193 std::unique_ptr<component> comp;
1194 };
1195
1196 // Reference to a component entry for filtered generation.
1197 struct component_ref
1198 {
1199 std::wstring key;
1200 std::reference_wrapper<const component> comp;
1201 double effective_weight;
1202 };
1203
1204 // Type alias for the observer list.
1205 using observer_list =
1206 std::vector<std::shared_ptr<generation_observer>>;
1207
1208 // Notify all observers in a list by calling a member function.
1209 template <typename Hook, typename... Args>
1210 static void notify_all(const observer_list& observers,
1211 Hook hook, Args&&... args)
1212 {
1213 for (const auto& obs : observers)
1214 {
1215 ((*obs).*hook)(std::forward<Args>(args)...);
1216 }
1217 }
1218
1219 // Convenience: notify using the instance's observer list.
1220 template <typename Hook, typename... Args>
1221 void notify(Hook hook, Args&&... args) const
1222 {
1223 notify_all(_observers, hook, std::forward<Args>(args)...);
1224 }
1225
1226 // Core generation logic shared by all overloads.
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)
1230 {
1231 entity generated_entity;
1232 generation_context ctx;
1233
1234 // Derive an entity-level seed and use it to create a local engine
1235 // for per-component seed derivation.
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)};
1239
1240 for (const auto& ref : components)
1241 {
1242 auto component_seed = static_cast<std::uint64_t>(local_engine());
1243
1244 // Roll against the effective weight. A seed is always consumed
1245 // to keep the deterministic sequence stable regardless of which
1246 // components are included.
1247 if (ref.effective_weight < 1.0)
1248 {
1249 std::uniform_real_distribution<double> dist(0.0, 1.0);
1250 if (dist(local_engine) >= ref.effective_weight)
1251 {
1252 notify_all(observers,
1254 continue;
1255 }
1256 }
1257
1258 // Conditional check: skip if the component opts out based on context.
1259 if (!ref.comp.get().should_generate(ctx))
1260 {
1261 notify_all(observers,
1263 continue;
1264 }
1265
1266 notify_all(observers,
1268
1269 ctx._random.seed(static_cast<std::mt19937::result_type>(component_seed));
1270 auto value = ref.comp.get().generate(ctx);
1271
1272 // Component-level validation with retries.
1273 for (std::size_t retry = 0;
1274 retry < max_retries && !ref.comp.get().validate(value);
1275 ++retry)
1276 {
1277 notify_all(observers,
1278 &generation_observer::on_before_retry, ref.key, retry + 1);
1279 component_seed = static_cast<std::uint64_t>(local_engine());
1280 ctx._random.seed(
1281 static_cast<std::mt19937::result_type>(component_seed));
1282 value = ref.comp.get().generate(ctx);
1283 notify_all(observers,
1285 retry + 1, value);
1286 }
1287
1288 if (!ref.comp.get().validate(value))
1289 {
1290 notify_all(observers,
1292 throw std::runtime_error(
1293 "component validation failed after max retries");
1294 }
1295
1296 auto display = ref.comp.get().to_string(value);
1297
1298 notify_all(observers,
1300
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});
1306 }
1307
1308 return generated_entity;
1309 }
1310
1311 // --- Generation helpers -----------------------------------------------
1312
1313 // Apply entity-level validation with retries.
1314 template <typename GenFn>
1315 [[nodiscard]] entity with_entity_validation(GenFn&& fn) const
1316 {
1317 for (std::size_t attempt = 0; attempt <= _max_retries; ++attempt)
1318 {
1319 if (attempt > 0)
1320 notify_all(_observers,
1322 auto e = fn();
1323 if (attempt > 0)
1324 notify_all(_observers,
1326 if (!_validator || _validator(e)) return e;
1327 }
1328
1329 notify_all(_observers, &generation_observer::on_entity_fail);
1330 throw std::runtime_error(
1331 "entity validation failed after max retries");
1332 }
1333
1334 // Wrap a generation function with before/after generate hooks.
1335 template <typename GenFn>
1336 [[nodiscard]] entity generate_with_hooks(GenFn&& fn) const
1337 {
1338 notify_all(_observers, &generation_observer::on_before_generate);
1339 auto e = with_entity_validation(std::forward<GenFn>(fn));
1340 notify_all(_observers,
1342 return e;
1343 }
1344
1345 // --- Component resolution ---------------------------------------------
1346
1347 // Build a component_ref span from the owned component entries.
1348 [[nodiscard]] std::vector<component_ref> all_component_refs() const
1349 {
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)};
1356 });
1357 return refs;
1358 }
1359
1360 // Filter registered components by the requested keys, preserving
1361 // registration order.
1362 [[nodiscard]] std::vector<component_ref> filter_components(
1363 std::span<const std::wstring> component_keys) const
1364 {
1365 std::vector<component_ref> filtered;
1366
1367 auto matching = _components
1368 | std::views::filter([&](const auto& entry) {
1369 return std::ranges::contains(component_keys, entry.key);
1370 })
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)};
1374 });
1375 std::ranges::copy(matching, std::back_inserter(filtered));
1376
1377 return filtered;
1378 }
1379
1380 // Resolve the effective weight for a component: override wins, then
1381 // the component's own weight() method.
1382 [[nodiscard]] double effective_weight(
1383 const std::wstring& key, const component& comp) const
1384 {
1385 auto it = _weight_overrides.find(key);
1386 return it != _weight_overrides.end() ? it->second : comp.weight();
1387 }
1388
1389 // Registered components in insertion order.
1390 std::vector<component_entry> _components;
1391
1392 // Named groups of component keys.
1393 std::map<std::wstring, std::vector<std::wstring>> _groups;
1394
1395 // Per-component weight overrides (key -> weight).
1396 std::map<std::wstring, double> _weight_overrides;
1397
1398 // Entity-level validation function (optional).
1399 std::function<bool(const entity&)> _validator;
1400
1401 // Generation lifecycle observers (optional).
1402 observer_list _observers;
1403
1404 // Maximum retries for component and entity validation.
1405 std::size_t _max_retries{10};
1406
1407 // Internal random engine for the generator.
1408 std::mt19937 _engine{std::random_device{}()};
1409};
1410} // namespace dasmig
1411
1412#endif // DASMIG_ENTITYGEN_HPP
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.
Definition entitygen.hpp:88
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.
Definition entitygen.hpp:39
bool has(const std::wstring &component_key) const
Check if a component value has already been generated.
Definition entitygen.hpp:44
T get(const std::wstring &component_key) const
Typed retrieval of an already-generated component value.
Definition entitygen.hpp:55
effolkronium::random_local & random() const
Access the random engine for this generation.
Definition entitygen.hpp:62
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").
Tag type indicating that generic components should use default_to_string().
A value–weight pair for weighted selection.
double weight
Relative selection weight (must be >= 0).