Systems¶
Systems contain the logic of the simulation. Each system declares which components it needs and processes all matching entities during the update loop.
Defining a system¶
Inherit from fr::System and override the lifecycle hooks you need:
#include <Freyr/Freyr.hpp>
class MovementSystem : public fr::System {
public:
explicit MovementSystem(const Ref<fr::Scene>& scene) : System(scene) {}
void Update(float deltaTime) override {
mScene->ForEachParallel<Position, Velocity>(
[deltaTime](fr::Entity, Position& pos, const Velocity& vel) {
pos.x += vel.dx * deltaTime;
pos.y += vel.dy * deltaTime;
pos.z += vel.dz * deltaTime;
});
}
};
The constructor must accept const Ref<fr::Scene>& as its first argument. Additional dependencies are resolved by Skirnir's DI container.
Lifecycle hooks¶
Systems are called in this order every frame by Scene::Update(dt):
PreUpdate(dt)
Update(dt)
PostUpdate(dt)
[if fixed timestep accumulated]:
PreFixedUpdate(dt)
FixedUpdate(dt)
PostFixedUpdate(dt)
| Hook | Typical use |
|---|---|
PreUpdate(dt) | Input gathering, reset accumulators |
Update(dt) | Main simulation logic |
PostUpdate(dt) | Post-processing, late reads |
PreFixedUpdate(dt) | Setup before physics step |
FixedUpdate(dt) | Physics, collision detection/resolution |
PostFixedUpdate(dt) | Constraints, correction |
All hooks have a default empty implementation — override only those you need.
Dependency injection¶
Systems are singletons registered in Skirnir's DI container. Any service registered in the container can be injected via the constructor:
class CollisionSystem : public fr::System {
public:
CollisionSystem(const Ref<fr::Scene>& scene, Ref<fr::EventManager> events)
: System(scene), mEvents(events) {}
void FixedUpdate(float dt) override {
mScene->ForEachParallel<Position, Collider>(
[this](fr::Entity a, Position& posA, Collider& colA) {
// detect collisions and publish events
mScene->SendEvent(CollisionEvent { .entityA = a });
});
}
private:
Ref<fr::EventManager> mEvents;
};
Registration¶
Register systems with FreyrExtension::WithSystem<T>(). Systems are instantiated and wired in registration order:
freyr
.WithSystem<InputSystem>() // registered first, runs first
.WithSystem<MovementSystem>()
.WithSystem<CollisionSystem>()
.WithSystem<RenderSystem>(); // registered last, runs last
System ID¶
Each system type has a unique runtime ID:
Accessing the scene¶
The protected mScene member provides access to all Scene operations:
void Update(float dt) override {
// iterate
mScene->ForEach<Health>([](fr::Entity e, Health& hp) { ... });
// create/destroy entities
mScene->DestroyEntity(deadEnemy);
// add/remove components
mScene->AddComponent<DeadTag>(entity);
// send events
mScene->SendEvent(MyEvent {});
}
Ordering and dependencies¶
Systems run in registration order, sequentially. If SystemB depends on results produced by SystemA, register SystemA first.
For data dependencies within a system, use ForEach (sequential) rather than ForEachParallel when the callback reads from other entities.