Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce new system Pipeline behavior #185

Merged
merged 1 commit into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 32 additions & 49 deletions doc/Tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,61 +433,44 @@ registry.runSystem<Movement>();
registry.runSystems();
```

By default systems added with `Registry.addSystem()` have the same priority and the order of execution is undefined when calling `Registry.runSystems()`.
But you can use the system priorities/groups to order them and be able to specify their priority and run a given system group.
### Pipeline

1. Priority
By default systems added with `Registry.addSystem()` run in the registration order. But you can use @ref ecstasy::Pipeline "pipelines" to have a better configuration of your systems run order.

When adding a system to the registry you can add a priority to each system as a second template parameter.
Calling `runSystems()` will run the systems in ascending priority order.
You can group your systems into @ref ecstasy::Pipeline::Phase "phases" which will have an associated priority, being also its identifier.
Some predefined phases already exists and may be enough for you: @ref ecstasy::Pipeline::PredefinedPhases "PredefinedPhases".

@warning
You can set the same priority for multiple systems but it doesn't ensure they will always run in the same order.

```cpp
ecstasy::Registry registry;

registry.addSystem<A, 1>();
registry.addSystem<B, 2>();
registry.addSystem<C, 3>();
registry.addSystem<D, 4>();
registry.addSystem<E, 5>();
registry.addSystem<F, 6>();
registry.runSystems(); // ABCDEF
// If you want you can still call systems one by one
registry.runSystem<A>(); // A
```

2. Group and masks
@note
If you don't specify a phase, the @ref ecstasy::Pipeline::PredefinedPhases::OnUpdate "OnUpdate" is used.

In fact their is nothing more than the priority value. But using bitmask you can create groups.
@warning
I strongly recommend you to use enum types, or const values/macros.

The following example use 8 bits for the group id and 8 for the system priority.
```cpp
ecstasy::Registry registry;

```cpp
ecstasy::Registry registry;
// Mask of the group part on the priority (bits 8->15)
size_t mask = 0xff00;
// Groups 'identifiers'
const size_t abc = 0x0100;
const size_t def = 0x0200;

// Group 'abc'
registry.addSystem<A, abc + 1>();
registry.addSystem<B, abc + 2>();
registry.addSystem<C, abc + 3>();
// Group 'def'
registry.addSystem<D, def + 1>();
registry.addSystem<E, def + 2>();
registry.addSystem<F, def + 3>();

// The following still calls the systems in the right order because their priorities are still ascending
registry.runSystems(); // ABCDEF

// You can run a system group by sending the group id and the mask
registry.runSystems(abc, mask); // ABC
registry.runSystems(def, mask); // DEF
```
// Default to OnUpdate
registry.addSystem<A>();
// Explicit OnUpdate, require to be casted as size_t if template parameter
registry.addSystem<B, static_cast<size_t>(Pipeline::PedefinedPhases::OnUpdate)>();
// Explicit OnLoad but using enum value directly
registry.addSystemInPhase<C>(Pipeline::PedefinedPhases::OnLoad);
registry.addSystem<D, 250>();
registry.addSystem<E, 251>();
registry.addSystem<F, Pipeline::PedefinedPhases::OnUpdate>();


// Will run in order:
// - OnLoad(100): C
// - Custom 250: D
// - Custom 251: E
// - OnUpdate(400): BCF
registry.runSystems();
// If you want you can still call systems one by one
registry.runSystem<A>(); // A
// Or even phase by phase
registry.runSystemsPhase<Pipeline::PredefinedPhases::OnUpdate>();
```

## Using resources

Expand Down
22 changes: 9 additions & 13 deletions src/ecstasy/registry/Registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace ecstasy
return _builder.build();
}

Registry::Registry(bool addEntities)
Registry::Registry(bool addEntities) : _pipeline(*this)
{
if (addEntities)
addResource<Entities>();
Expand Down Expand Up @@ -81,23 +81,19 @@ namespace ecstasy
storage.second->erase(entities);
}

void Registry::runSystem(const std::type_index &systemId)
{
_systems.get(systemId).run(*this);
}

void Registry::runSystems()
{
for (auto &[type, system] : _systems.getInner())
system->run(*this);
_pipeline.run();
}

void Registry::runSystems(size_t group, size_t mask)
void Registry::runSystemsPhase(Pipeline::PhaseId phase)
{
auto &systems = _systems.getInner();
auto it = systems.begin();

// Run all systems with the same group and mask
while (it != systems.end()) {
if ((it->first.second & mask) == group)
it->second->run(*this);
++it;
}
_pipeline.run(phase);
}

void Registry::clear()
Expand Down
95 changes: 79 additions & 16 deletions src/ecstasy/registry/Registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "ecstasy/storages/StorageConcepts.hpp"
#include "ecstasy/storages/SystemInstances.hpp"
#include "ecstasy/system/ISystem.hpp"
#include "ecstasy/system/Pipeline.hpp"
#include "ecstasy/thread/LockableView.hpp"
#include "util/Allocator.hpp"
#include "util/StackAllocator.hpp"
Expand All @@ -34,6 +35,7 @@
#include "concepts/component_type.hpp"
#include "concepts/queryable_type.hpp"
#include "ecstasy/registry/concepts/modifier_allocator_size.hpp"
#include "util/meta/is_size_t_convertible.hpp"
#include "util/meta/outer_join.hpp"

#ifdef _MSC_VER
Expand Down Expand Up @@ -815,8 +817,11 @@ namespace ecstasy
///
/// @brief Add a new system in the registry.
///
/// @note Call @ref addSystemInPhase to add a system in a specific phase using runtime phase id or an Enum type
/// for phase id.
///
/// @tparam S System to add.
/// @tparam Priority System priority. See @ref Registry::runSystems().
/// @tparam Phase System phase. See @ref Pipeline.
/// @tparam Args The type of arguments to pass to the constructor of @b S.
///
/// @param[in] args The arguments to pass to the constructor of @b S.
Expand All @@ -828,10 +833,36 @@ namespace ecstasy
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2022-10-17)
///
template <std::derived_from<ISystem> S, size_t Priority = 0, typename... Args>
template <std::derived_from<ISystem> S,
Pipeline::PhaseId Phase = static_cast<Pipeline::PhaseId>(Pipeline::PredefinedPhases::OnUpdate),
typename... Args>
S &addSystem(Args &&...args)
{
return _systems.emplace<S, Priority>(std::forward<Args>(args)...);
return addSystemInPhase<S>(Phase, std::forward<Args>(args)...);
}

///
/// @brief Add a new system in the registry in a specific phase.
///
/// @tparam S System to add.
/// @tparam T Phase id type. Usually an enum convertible to size_t, or a size_t directly.
/// @tparam Args The type of arguments to pass to the constructor of @b S.
///
/// @param[in] phaseId Phase id where to add the system.
/// @param[in] args The arguments to pass to the constructor of @b S.
///
/// @return S& newly created System.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-11-21)
///
template <std::derived_from<ISystem> S, util::meta::is_size_t_convertible T, typename... Args>
S &addSystemInPhase(T phaseId, Args &&...args)
{
S &system = _systems.emplace<S>(std::forward<Args>(args)...);

_pipeline.addSystem(typeid(S), static_cast<size_t>(phaseId));
return system;
}

///
Expand Down Expand Up @@ -1170,32 +1201,36 @@ namespace ecstasy
_systems.get<S>().run(*this);
}

///
/// @brief Run a specific system from the registry.
///
/// @param[in] systemId System type index to run.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-11-21)
///
void runSystem(const std::type_index &systemId);

///
/// @brief Run all systems present in the registry.
///
/// @note Systems are run in ascending priority order. If two systems have the same priority, run order is
/// undefined.
/// @note Systems are run in ascending registration order. You can have further control using
/// @ref ecstasy::Pipeline::Phase "Phases".
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2022-10-17)
///
void runSystems();

///
/// @brief Run all systems whose priority match the given group.
/// @brief Run all systems present in the registry for the given phase.
///
/// @note The system groups can be seen as an internet network: The @p group 'id' is the network address, the
/// @p mask is the network mask and each system priority in the group is a host in the network range.
/// @param[in] phase Phase to run the systems for.
///
/// @note Systems in the group are run in ascending priority order. If two systems have the same priority, run
/// order is undefined.
///
/// @param[in] group Group priority 'id'.
/// @param[in] mask Priority 'id' mask.
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2022-12-19)
/// @since 1.0.0 (2024-11-21)
///
void runSystems(size_t group, size_t mask);
void runSystemsPhase(Pipeline::PhaseId phase);

///
/// @brief Get a const reference to the storages instances.
Expand Down Expand Up @@ -1223,13 +1258,41 @@ namespace ecstasy
return _storages;
}

///
/// @brief Get a reference to the registry pipeline.
///
/// @return Pipeline& Reference to the registry pipeline instance.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-11-21)
///
[[nodiscard]] constexpr Pipeline &getPipeline() noexcept
{
return _pipeline;
}

///
/// @brief Get a const reference to the registry pipeline.
///
/// @return const Pipeline& Const reference to the registry pipeline instance.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-11-21)
///
[[nodiscard]] constexpr const Pipeline &getPipeline() const noexcept
{
return _pipeline;
}

private:
/// @brief Registry resources.
Instances<IResource> _resources;
/// @brief Registry storages.
Instances<IStorage> _storages;
/// @brief Registry systems.
SystemInstances _systems;
Instances<ISystem> _systems;
/// @brief System pipeline.
Pipeline _pipeline;

/// @internal
/// @brief Erase all the @p entity components.
Expand Down
2 changes: 2 additions & 0 deletions src/ecstasy/system/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ set(SRCROOT ${SRCROOT}/system)

set(SRC
${SRC}
${INCROOT}/Pipeline.hpp
${SRCROOT}/Pipeline.cpp
${INCROOT}/ISystem.hpp
PARENT_SCOPE
)
Loading
Loading