registry: An entt::registry with a few added featrues.
system: A function that gets executed in the application.
resource: Globally unique data accessible from systems.
There are two types: Resource and Local
Resource's are shared between systems. E.g. if you modify a Resource from system A that change is visible by system B.
Local's are unique to systems. E.g. if you modify a Local from system A that only affects the Local for that specific system. Other systems cannot view/modify other system's Locals.
world: An aggregation of resources and the registry.
stage: A collection of systems that can possibly be run in parallel.
scheduler: The collection of stages that are run in order.
label: An identifier used for ordering and lookup.
bundle: A logical collection of components. It is NOT itself a component.
Labels
A label is used as an identifier for ordering systems and stages.
They can be constructed from multiple types including:
Empty structs
String-like things
Systems
structMyLabel {};
auto system1 = [](nova::World&) {};
auto system2 = [](nova::World&) {};
app.add_system(nova::system(system1)
// string-like things
.label("a")
.label(std::string{"string"})
// empty structs
.after<MyLabel>() // .after(MyLabel{}) <-- also valid syntax for empty structs// other systems
.before(system2));
Stages
A stage is nothing other than a collection of systems.
An application can have any number of stages. These stages can (like systems) be ordered.
// Adding a stageauto app = nova::App{};
structMyTagStage {};
// stages can be `tag` types
app.add_stage<MyTagStage>();
// or stages can be made from string like types
app.add_stage("my stage");
// stages can be ordered (just like systems)
app.add_stage(
nova::stage("my other stage")
.after("my stage")
.after<nova::stages::Update>()
.before<MyTagStage>());
By default, nova has 5 stages that are run in order.
First
PreUpdate
Update
PostUpdate
Last
If a system is added to the app without specifying the stage, it will be added to the Update stage.
Systems
A system is nothing other than a function which operates on a subset of the World.
Systems can take any number of parameters, however, they are limited to their type.
Notably, a system can be a normal function, a lambda, or any other invocable like object.
// a normal functionautomy_system(nova::World& world) { /* ... */ }
// or a lambdaauto my_system = [](nova::World& world) {};
Startup/Teardown Systems
A startup system is one that runs ONCE at the beginning of an app.
A teardown system is one that runs ONCE at the end of an app.
Resource<T>: a global resource shared between systems.
Must be initialized via App::insert_resource otherwise an exception will be thrown.
Option<Resource<T>>:
If it is unknown if a resource exists, use this type. As it will not throw an exception if the resource does not exist.
(const) Registry&: a reference to the underlying entt::registry.
(const) Resources&: a reference to all global resources.
(const) World&: a reference to the applications' World.
View<With<...>, Without<...>>: A simple view over all entities with a specific criteria of components.
This is exactly the same as the type returned from entt::registry::view<...>(...).
Adding & Ordering Systems
Systems can be added to an application via a simple add_system() call.
automy_system(nova::World& world) {}
auto app = nova::App{};
// be default, this is added to the `Update` stage.
app.add_system(my_system);
Or they can be added to specific stages.
app.add_system_to_stage<stages::PreUpdate>(my_system);
app.add_system_to_stage(my_other_system, "my custom stage");
Ordering systems is very simple!
autosystem_a() -> void {}
autosystem_b() -> void {}
// We are labeling the system 'a'
app.add_system(system(system_a).label("a"));
// We are ordering this systems *after* 'a'// This means it will run *after* system 'a' completes.
app.add_system(system(system_b).after("a"));
SystemSet
A system_set is merely a way to assign similar labels/criteria to multiple systems.
// instead of (note how both systems have the exact same labels/ordering)
app.add_system(nova::system(system1).label("a").after("b"))
.add_system(nova::system(system2).label("a").after("b"));
// Use a system_set :)
app.add_system(nova::system_set()
.with_system(system1)
.with_system(system2)
.label("a")
.after("b"));
A note on const-ness.
When using any of the following system parameters:
Resource<T>, World, Registry, Resources, or View<...>
It is important to mark the immutable uses of them as const.
As games are usually run in a multi-threaded environment, when two systems operate over mutable access to the same value, they CANNOT be run at the same time as this would cause a data race.
However, if you have two systems that operate on disjoint set of types, or share only immutable access to shared types, they CAN run at the same time.
structcomponent { /* ... */ };
// immutable access to `component`autosystem_a(View<With<const component>>) {}
autosystem_b(View<With<const component>>) {}
// mutable access to `component`autosystem_c(View<With<component>>) {}
/*Since `system_a` and `system_b` have a CONST (immutable) view over `component` they can run at the same time.Since `system_c` has NON CONST (mutable) access to `component` it cannot run at the same time as either `system_a` nor `system_b`.*/