This guide covers every feature of the entity-generator library in detail. For a quick overview, see the README. For the full API reference, run doxygen Doxyfile from the repository root and open doc/api/html/index.html.
Defining Components
{
public:
[[nodiscard]] std::wstring
key()
const override {
return L
"class"; }
{
static const std::vector<std::wstring> classes{
L"Warrior", L"Mage", L"Rogue", L"Healer"};
return ctx.
random().get(classes);
}
[[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
{
}
};
{
public:
[[nodiscard]] std::wstring
key()
const override {
return L
"age"; }
{
return ctx.
random().get(18, 65);
}
[[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
{
}
};
Abstract base for entity components.
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.
static std::wstring default_to_string(const std::any &value)
Default conversion covering common standard types.
virtual std::wstring key() const =0
Unique key identifying this component (e.g. "name", "age").
Context passed to components during generation.
effolkronium::random_local & random() const
Access the random engine for this generation.
Entity generator library — composable, deterministic entity generation for C++23.
default_to_string() handles std::wstring, int, double, float, long, and bool. All other types return [?] — override to_string() for custom types.
Registering and Generating
eg::instance()
.
add(std::make_unique<character_class>())
.
add(std::make_unique<age>());
auto entity = eg::instance().
generate();
auto partial = eg::instance().generate({L"class"});
std::wcout << eg::instance().generate() << std::endl;
auto seeded = eg::instance().generate(42);
eg::instance().
seed(123);
auto first = eg::instance().generate();
auto second = eg::instance().generate();
eg::instance().unseed();
The entity generator — produces entities with configurable components.
entity generate()
Generate an entity with all registered components.
eg & add(std::unique_ptr< component > comp)
Register a component.
std::uint64_t seed(const std::wstring &component_key) const
Retrieve the random seed used to generate a specific component.
Behavior notes:
add() with a key that already exists replaces the component in place, preserving registration order. Any existing weight override for that key persists.
remove() on a non-existent key is a no-op (does not throw).
remove_group() on a non-existent group is a no-op.
Typed Retrieval
auto entity = eg::instance().generate();
std::wstring char_class = entity.get<std::wstring>(L"class");
int char_age = entity.get<int>(L"age");
const auto& raw = entity.get_any(L"class");
if (entity.has(L"name")) { }
for (const auto& key : entity.keys()) { }
Component Dependencies
Components receive a generation_context that provides access to values generated by earlier components. Registration order determines generation order.
{
public:
[[nodiscard]] std::wstring
key()
const override {
return L
"greeting"; }
{
auto name = ctx.
get<std::wstring>(L
"name");
return L"Hello, " + name + L"!";
}
[[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
{
}
};
eg::instance()
.add(std::make_unique<character_name>())
.add(std::make_unique<greeting>());
T get(const std::wstring &component_key) const
Typed retrieval of an already-generated component value.
The context also exposes ctx.has(key) to check whether a dependency was generated (useful when generating with a subset of components).
Composing with Other Generators
Components can wrap name-generator and nickname-generator to bring in name and nickname generation:
#include <dasmig/namegen.hpp>
#include <dasmig/nicknamegen.hpp>
{
public:
[[nodiscard]] std::wstring
key()
const override {
return L
"name"; }
{
return static_cast<std::wstring>(
dasmig::ng::instance().get_name().append_surname());
}
[[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
{
}
};
{
public:
[[nodiscard]] std::wstring
key()
const override {
return L
"nickname"; }
{
return static_cast<std::wstring>(
dasmig::nng::instance().get_nickname());
}
[[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
{
}
};
Custom Types
Components can produce any type. Override to_string() on your component to enable stream output:
struct vec2 { float x, y; };
{
public:
[[nodiscard]] std::wstring
key()
const override {
return L
"position"; }
{
return vec2{
ctx.
random().get(-100.f, 100.f),
ctx.
random().get(-100.f, 100.f)};
}
[[nodiscard]] std::wstring
to_string(
const std::any& value)
const override
{
auto pos = std::any_cast<vec2>(value);
return L"(" + std::to_wstring(pos.x) + L", " + std::to_wstring(pos.y) + L")";
}
};
Seed Signatures
Every generated entity and component stores the random seed that produced it. This enables replay, debugging, and logging.
auto entity = eg::instance().generate();
std::uint64_t entity_seed = entity.seed();
std::uint64_t age_seed = entity.seed(L"age");
Component seeds can be used to replay individual component generation:
effolkronium::random_local rng;
rng.seed(static_cast<std::mt19937::result_type>(entity.seed(L"age")));
int replayed_age = rng.get(18, 65);
The seed hierarchy is fully deterministic:
generation seed (per-call or generator-level)
└─ entity seed ← entity.seed()
└─ local engine
├─ component A seed ← entity.seed(L"A")
├─ component B seed ← entity.seed(L"B")
└─ ...
Batch Generation
Generate multiple entities in a single call:
auto batch = eg::instance().generate_batch(10);
for (const auto& e : batch)
{
std::wcout << e << L'\n';
}
auto seeded_batch = eg::instance().generate_batch(10, 42);
Component Groups
Define named groups of component keys for convenient selective generation:
eg::instance()
.add(std::make_unique<character_name>())
.add(std::make_unique<character_class>())
.add(std::make_unique<age>())
.add(std::make_unique<level>())
.add(std::make_unique<character_stats>());
eg::instance()
.add_group(L"identity", {L"name", L"class"})
.add_group(L"combat", {L"class", L"level", L"stats"});
auto fighter = eg::instance().generate_group(L"combat");
auto seeded = eg::instance().generate_group(L"combat", 42);
if (eg::instance().has_group(L"identity")) { }
eg::instance().remove_group(L"identity");
Thread Safety
The eg class supports independent instances. Create one per thread for lock-free concurrent generation:
std::vector<std::jthread> threads;
for (int i = 0; i < 4; ++i)
{
threads.emplace_back([&](int id) {
gen.
add(std::make_unique<name>());
}, i);
}
eg & seed(std::uint64_t seed_value)
Seed the internal random engine.
eg is move-constructible and move-assignable, so instances can be transferred between scopes. The instance() singleton is still available for single-threaded convenience.
Component Weights
Components can declare an inclusion weight via the weight() virtual method (default 1.0). Values range from 0.0 (never included) to 1.0 (always included). The generator rolls against the weight during generation; components that fail the roll are skipped.
{
public:
std::wstring
key()
const override {
return L
"rare_trait"; }
double weight()
const override {
return 0.2; }
{
return ctx.
random().get<std::wstring>({L
"scar", L
"tattoo", L
"birthmark"});
}
std::wstring
to_string(
const std::any& value)
const override
{
}
};
virtual double weight() const
Inclusion weight for this component (0.0 to 1.0).
Weights can also be overridden at registration time or updated later, taking precedence over the component's own weight() method:
eg::instance().add(std::make_unique<rare_trait>(), 0.5);
eg::instance().weight(L"rare_trait", 0.8);
Note: When a weighted component is skipped, dependent components that call ctx.get<T>() for it will throw. Use ctx.has() to guard dependency access in weight-sensitive components.
Entity Serialization
Convert an entity to a formatted string with to_string(). Each component is rendered as "key: display" separated by two spaces. The output matches operator<<.
auto entity = eg::instance().generate();
std::wstring text = entity.to_string();
std::wcout << entity << L'\n';
Structured Serialization
to_map() returns component values as a std::map<std::wstring, std::wstring> mapping keys to their display strings. This is convenient for integration with JSON, XML, or any key–value serialization format.
auto entity = eg::instance().generate();
auto m = entity.to_map();
for (const auto& [key, value] : m)
{
std::wcout << key << L" = " << value << L'\n';
}
Custom types work seamlessly — the map uses each component's to_string() output:
auto m = entity.to_map();
Concurrent Batch Generation
generate_batch_async() generates entities in parallel using std::async. Entity seeds are pre-derived sequentially from the engine, then each entity is generated in its own async task. Results are returned in seed order.
auto batch = eg::instance().generate_batch_async(100);
auto batch1 = eg::instance().generate_batch_async(50, 42);
auto batch2 = eg::instance().generate_batch_async(50, 42);
Entity-level validation applies to each concurrent task independently. If an observer is set, the caller is responsible for its thread safety (hooks fire from multiple threads concurrently).
Validation Hooks
Validation operates at two levels, both opt-in with zero-cost defaults.
Component-level: Override validate() on your component. If it returns false, the generator re-rolls with a new seed up to max_retries times, then throws std::runtime_error.
{
public:
std::wstring
key()
const override {
return L
"age"; }
{
return ctx.
random().get(18, 65);
}
bool validate(
const std::any& value)
const override
{
return std::any_cast<int>(value) % 2 == 0;
}
std::wstring
to_string(
const std::any& value)
const override
{
}
};
virtual bool validate(const std::any &value) const
Validate a generated value.
Entity-level: Set a callback on the generator. If it returns false, the entire entity is re-generated up to max_retries additional times.
return e.
get<
int>(L
"age") > 30;
});
auto e = eg::instance().generate();
eg::instance().clear_validator();
A generated entity holding component values in registration order.
T get(const std::wstring &component_key) const
Typed retrieval of a component value by key.
Configure the retry limit (default 10) for both levels:
eg::instance().max_retries(20);
Event Hooks
Attach one or more generation_observer instances to any generator to receive lifecycle callbacks. Override only the hooks you need; all default to no-ops.
{
public:
{
std::wcout << L"generating entity...\n";
}
const std::any& ) override
{
std::wcout << L"produced " << key << L'\n';
}
};
auto obs = std::make_shared<log_observer>();
eg::instance().add_observer(obs);
eg::instance().generate();
eg::instance().remove_observer(obs);
eg::instance().clear_observers();
Observer interface for hooking into generation lifecycle events.
virtual void on_after_component(const std::wstring &key, const std::any &value)
Called after a component is successfully generated.
virtual void on_before_generate()
Called before an entity is generated.
Multiple observers fire in registration order. The same observer can be added more than once.
Thread safety: Observers are not thread-safe by default. When using generate_batch_async, the caller is responsible for ensuring observer implementations are safe for concurrent invocation (e.g., using atomics or mutexes).
The full set of hooks (6 before/after pairs + 3 single-fire hooks, 15 methods):
| Event | Hook(s) |
| Entity generation | on_before_generate() / on_after_generate(entity) |
| Component generation | on_before_component(key) / on_after_component(key, value) |
| Component skip | on_skip(key) |
| Component retry | on_before_retry(key, attempt) / on_after_retry(key, attempt, value) |
| Component fail | on_component_fail(key) |
| Entity retry | on_before_entity_retry(attempt) / on_after_entity_retry(attempt) |
| Entity fail | on_entity_fail() |
| Registration | on_before_add(key) / on_after_add(key) |
| Removal | on_before_remove(key) / on_after_remove(key) |
For per-component filtering, check the key parameter inside your hook override.
Conditional Components
Components can override should_generate(ctx) to conditionally skip generation based on the current context. Unlike weights (probabilistic), this is logic-driven. The default returns true.
{
public:
std::wstring
key()
const override {
return L
"title"; }
{
static const std::vector<std::wstring> titles{
L"Warlord", L"Champion", L"Berserker"};
return ctx.
random().get(titles);
}
{
&& ctx.
get<std::wstring>(L
"class") == L
"Warrior";
}
std::wstring
to_string(
const std::any& value)
const override
{
}
};
virtual bool should_generate(const generation_context &ctx) const
Decide whether this component should be generated.
bool has(const std::wstring &component_key) const
Check if a component value has already been generated.
When should_generate returns false, the component is skipped entirely — no value is produced and entity.has(key) returns false. The skip fires the same observer hooks as a weight skip (on_before_skip / on_after_skip).
Note: should_generate is checked after the weight roll. If a component is excluded by weight, should_generate is never called.
Generic Components
Five reusable class templates for the most common component patterns. All live in the dasmig:: namespace and accept an optional custom formatter as a trailing template parameter.
constant_component<T>
Always returns the same value.
L"label", L"npc"));
Component that always returns a fixed value.
choice_component<T>
Picks uniformly at random from a list.
L"class", std::vector<std::wstring>{L"Warrior", L"Mage", L"Rogue"}));
Component that picks uniformly at random from a list of values.
range_component<T>
Uniform random in a numeric range [lo, hi]. Restricted to arithmetic types.
Component that generates a uniform random value in [lo, hi].
callback_component
Wraps a callable that takes generation_context& and returns a value. Avoids subclassing for one-off or context-dependent components. Use std::function to erase the callable type for make_unique.
gen.
add(std::make_unique<cb_wstr>(
return L
"Hello, " + ctx.
get<std::wstring>(L
"name") + L
"!";
}));
Component that wraps a callable for one-off or computed values.
A CTAD deduction guide is provided for stack-allocated usage where make_unique is not needed.
weighted_choice_component<T>
Picks from a list of values using per-option weights. Weights are relative (they don't need to sum to 1.0). A weight of 0 means the option is never selected.
gen.
add(std::make_unique<rarity_t>(
L"rarity", std::vector<rarity_t::option>{
{L"Common", 60.0}, {L"Uncommon", 25.0},
{L"Rare", 10.0}, {L"Legendary", 5.0}}));
Component that picks from a list of values using per-option weights.
Custom formatters
All generic components use default_to_string by default. Pass a custom formatter as an extra template parameter and constructor argument for types it doesn't handle:
auto fmt = [](int v) { return L"#" + std::to_wstring(v); };
L"id", 5, fmt));
Entity Introspection
auto entity = eg::instance().generate();
std::size_t n = entity.size();
if (entity.empty()) { }
auto keys = entity.keys();
const std::any& val = entity.get_any(L"class");
auto entity_seed = entity.seed();
auto comp_seed = entity.seed(L"class");
auto m = entity.to_map();
Generator Introspection
gen.
add(std::make_unique<age>())
.
add(std::make_unique<character_class>());
std::size_t n = gen.
size();
bool has_age = gen.
has(L
"age");
bool has(const std::wstring &component_key) const
Check if a component is registered by key.
std::vector< std::wstring > component_keys() const
Return all registered component keys in registration order.
eg & clear()
Remove all registered components, weight overrides, and groups.
std::size_t size() const
Return the number of registered components.
Extensions
Optional headers in dasmig/ext/ provide ready-made functionality built on the observer interface.
stats_observer
#include <dasmig/ext/stats_observer.hpp>
Tracks comprehensive generation statistics: counts, timing, per-component breakdowns, and value distributions. Attach it like any other observer; read the fields after generation.
auto stats = std::make_shared<dasmig::ext::stats_observer>();
std::wcout << L"Entities: " << stats->entities_generated << L'\n'
<< L"Components: " << stats->components_generated << L'\n'
<< L"Skipped: " << stats->components_skipped << L'\n'
<< L"Failures: " << stats->component_failures << L'\n';
for (const auto& [key, n] : stats->component_counts)
std::wcout << key << L": " << n << L" generated\n";
for (const auto& [key, n] : stats->component_retries)
std::wcout << key << L": " << n << L" retries\n";
using ms = std::chrono::milliseconds;
std::wcout << L"Avg entity: "
<< std::chrono::duration_cast<ms>(stats->avg_entity_time()).count()
<< L" ms\n";
for (const auto& [key, _] : stats->component_counts)
std::wcout << key << L" avg: "
<< std::chrono::duration_cast<ms>(
stats->avg_component_time(key)).count()
<< L" ms\n";
for (const auto& [key, dist] : stats->value_distribution)
for (const auto& [val, count] : dist)
std::wcout << key << L"=" << val << L": " << count << L'\n';
stats->reset();
std::wcout << stats->report();
std::wcout << *stats;
std::vector< entity > generate_batch(std::size_t count)
Generate multiple entities with all registered components.
eg & add_observer(std::shared_ptr< generation_observer > obs)
Add an observer to receive generation lifecycle callbacks.
Entity counters:
| Field | Description |
entities_generated | Successful entities produced |
entity_retries | Entity-level validation retries |
entity_failures | Entity validation exhausted (threw) |
Component counters:
| Field | Description |
components_generated | Total component values produced |
components_skipped | Weight or conditional skips |
component_failures | Component validation exhausted (threw) |
Per-component-key maps (all std::map<std::wstring, …>):
| Field | Value type | Description |
component_counts | size_t | Times each key was generated |
component_skip_counts | size_t | Times each key was skipped |
component_failure_counts | size_t | Times each key failed |
component_retries | size_t | Total retries per key |
component_times | duration | Cumulative wall-clock per key |
component_min_times | duration | Fastest generation per key |
component_max_times | duration | Slowest generation per key |
Entity timing:
| Field | Description |
total_generation_time | Sum of wall-clock across all entities |
min_entity_time | Fastest entity generation |
max_entity_time | Slowest entity generation |
Components-per-entity:
| Field | Description |
min_components_per_entity | Fewest components in a single entity |
max_components_per_entity | Most components in a single entity |
total_components_in_entities | Sum for computing averages |
Value distribution:
value_distribution is a map<wstring, map<wstring, size_t>> — for each component key, counts how many times each display value appeared. Common std::any types (wstring, int, double, float, long, bool) are stringified automatically; others record as <other>.
Computed helpers:
| Method | Returns |
avg_entity_time() | total_generation_time / entities_generated |
avg_component_time(key) | per-key average generation time |
avg_components_per_entity() | double average |
component_retry_rate() | total retries / components generated |
entity_retry_rate() | entity retries / entities generated |
ECS Integration: EnTT
dasmig/ext/entt_adapter.hpp bridges entity-generator with EnTT. It maps generator component keys to typed EnTT components via user-defined callables.
Requires: EnTT header (<entt/entt.hpp>) available on the include path.
struct Name { std::wstring value; };
struct Position { float x, y; };
entt::registry registry;
return Name{e.
get<std::wstring>(L
"name")};
});
return Position{e.
get<
float>(L
"x"), e.
get<
float>(L
"y")};
});
auto entt_entity = adapter.spawn(gen.
generate());
registry.get<Name>(entt_entity);
registry.get<Position>(entt_entity);
auto entities = adapter.spawn_batch(batch);
Adapter that bridges entity-generator with EnTT.
Adapter bridging entity-generator with EnTT.
Direct mapping (no transform, when generator value type == ECS type):
Spawn into existing entity:
auto prefab = registry.create();
registry.emplace<some_tag>(prefab);
adapter.spawn_into(prefab, gen.
generate());
Skipped components (weight = 0, conditional = false) are silently ignored — no EnTT component is emplaced for that key.
ECS Integration: Flecs
dasmig/ext/flecs_adapter.hpp bridges entity-generator with Flecs. Same mapping pattern as the EnTT adapter.
Requires: Flecs header (<flecs.h>) and compiled flecs.c linked.
struct Name { std::wstring value; };
struct Position { float x, y; };
flecs::world world;
return Name{e.
get<std::wstring>(L
"name")};
});
return Position{e.
get<
float>(L
"x"), e.
get<
float>(L
"y")};
});
auto flecs_entity = adapter.spawn(gen.
generate());
flecs_entity.get<Name>();
flecs_entity.get<Position>();
auto prefab = world.entity();
adapter.spawn_into(prefab, gen.
generate());
Adapter that bridges entity-generator with Flecs.
Adapter bridging entity-generator with Flecs.
API summary (identical for both adapters):
| Method | Description |
map<T>(key, fn) | Register a key→component mapping with transform |
map<T>(key) | Direct mapping (value type must match T) |
spawn(entity) | Create ECS entity with mapped components |
spawn_into(target, entity) | Apply mapped components to existing entity |
spawn_batch(vector) | Spawn multiple entities at once |
clear_mappings() | Remove all registered mappings |
Error Reference
The library throws standard exceptions. No custom exception types are used.
| Exception | Thrown by | Condition |
std::out_of_range | entity::get<T>(key), entity::get_any(key) | Key not present in the entity |
std::out_of_range | entity::seed(key) | Component key not found |
std::out_of_range | eg::weight(key, value) | Component not registered |
std::out_of_range | eg::generate_group(name) | Group not found |
std::runtime_error | eg::generate() (all overloads) | Component validation exhausted after max_retries |
std::runtime_error | eg::generate() (all overloads) | Entity validation exhausted after max_retries |
std::invalid_argument | choice_component constructor | Empty choices vector |
std::invalid_argument | weighted_choice_component constructor | Empty options vector or all weights are zero |
std::bad_any_cast | entity::get<T>(key), generation_context::get<T>(key) | Type mismatch on the stored value |