Systems Architecture Documentation
Systems Architecture is a framework that focuses on modular and scalable game logic.
It rewards using DOD (Data-Oriented-Design) over industry-wide OOP standard for writing game logic to reduce occurrence of common pitfals when doing OOP wrong, by avoiding it as much as possible and by that reducing comprehensive complexity to bare minimum.
In this documentation you will learn best practices for producing modular and scalable game systems that together make complex game features, yet still doesn't require countless hours of organization, production and iteration times in the process of making games.
Systems Architecture is refreshingly simple yet powerful tool to make Unreal Engine game developers Work Smart, Not Hard TM and we are gonna achieve that by reducing complexity of codebase and production process to bare minimum!
Ok, enought pretty words - let's dive into how Systems Architecture works and what problems does it solve.
Contents
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Book
This book covers explanation of Systems Architecture, as well as provides learning materials such as guidelines with best practices to keep your project healthy and your sanity in check, it also provides tutorial showing step-by-step how to use Systems Architecture in your game.
Pages
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Getting started
Before we jump into the meat of Systems Architecture, let me quickly show you a snippet showcasing how working with Systems looks like in a gist:
Click to unfold code snippet
// Actor componetns are treated as data/tags put on actors, that user reads from
// or writes to, using system queries.
UCLASS(BlueprintType, Blueprintable, Meta = (BlueprintSpawnableComponent))
class EXAMPLE_API UPlayerComponent : public USystemsActorComponent
{
GENERATED_BODY()
};
UCLASS(BlueprintType, Blueprintable, Meta = (BlueprintSpawnableComponent))
class EXAMPLE_API UEnemyComponent : public USystemsActorComponent
{
GENERATED_BODY()
};
// System are usually lambdas/functions that process chunks of systems world
// data (it can be querying actor components, managing resources or just
// processing some data - the point is we focus on processing game data, no
// matter its form).
void HuntSystem(USystemsWorld& Systems)
{
const auto DeltaTime = Systems.GetWorld()->GetDeltaSeconds();
// Systems Architecture adds an interface over game world that allows us to
// treat it as database that we can query, also rather than putting logic
// into each object, we rather perform batched data processing on a set of
// requested components.
for (auto& PlayerData : Systems.Query<UPlayerComponent>())
{
// Queries always yield tuple with actor that owns components
// requested for query, in addition to following components.
auto* PlayerActor = PlayerData.Get<0>();
const auto PlayerPosition = PlayerActor->GetActorLocation();
const auto Nearest = Systems
.Query<UEnemyComponent>()
// Mapping iterator is one of the most commonly used
// types of iterators that transform data from one form
// to another, usually to get exact information needed
// by next iterators in chain, or to just process it and
// collect the result.
.Map<TTuple<FVector, float>>(
[&](const auto& EnemyData)
{
const auto* EnemyActor = EnemyData.Get<0>();
const auto EnemyPosition = EnemyActor->GetActorLocation();
const auto Distance = FVector::Distance(PlayerPosition, EnemyPosition);
return MakeTuple(EnemyPosition, Distance);
})
// Finding the nearest enemy based on distance extracted
// in previous iteration step.
.ComparedBy(
[](const auto& A, const auto& B)
{
const auto DistanceA = A.Get<1>();
const auto DistanceB = B.Get<1>();
return DistanceA < DistanceB;
});
if (Nearest.IsSet())
{
const auto TargetPosition = Nearest.GetValue().Get<0>();
const auto Direction = (TargetPosition - PlayerPosition).GetSafeNormal();
const auto Velocity = Direction * 100.0f * DeltaTime;
PlayerActor->SetActorLocation(PlayerPosition + Velocity);
}
}
}
UCLASS()
class EXAMPLE_API UExampleGameInstance : public UGameInstance
{
GENERATED_BODY()
private:
virtual void Init() override;
};
void UExampleGameInstance::Init()
{
Super::Init();
auto* Subsystem = USystemsSubsystem::Get(GetWorld());
if (IsValid(Subsystem))
{
Subsystem->AcquireSystemsWorld(FName(),
[&](auto& Systems)
{
// Due to how queries are resolved, we need to register all
// components we might want to query.
Systems.RegisterComponent<UPlayerComponent>();
Systems.RegisterComponent<UEnemyComponent>();
// Persistent systems (ones that will run on every game tick)
// have to be registered in systems world. There are many types
// of systems, but commonly used ones are state-less
// lambda/function systems. System labels
// ("Hunt") are used for resolving systems dependency graph
// (user can provide sets of systems that given one has to run
// before and after).
Systems.InstallLambdaSystem(HuntSystem, FInstallSystemOptions("Hunt"));
});
}
}
Table of contents
Systems Architecture is not an ECS!
This is very important to say upfront:
Although at first glance Systems Architecture API looks somewhat like ECS, it definitely is not one.
There are similarities in both, but ECS do more than treat game as database, and its design choices revolve around best performance possible for massive worlds and making improved productivity and reduction of complexity is a side-effect of that, while Systems Architecture doesn't change much to the Unreal Engine API, rather resuses its parts (like actors and actor components) and only adds an API to enable accessing and processing game world in database-like fashion, making Systems Architecture focusing mostly on ergonomics and reducement of overall complexity.
That doesn't mean Systems Architecture doesn't care about performance - it already makes its internals do as less work as possible, and more optimizations are gonna arrive in further development, but the grand goal is and will be always making Unreal Engine game developers life as easier as possible.
That being said, Systems Architecture took great amount of inspiration from ECS, mostly from Rust ECS-based libraries and game engines that proven over time that database-like DOD approach to game logic is not only viable, but also highly beneficial for faster and easier game development process:
As well as Rust lazy-iterators
and channel
APIs.
Goals
More Data-Oriented-Design approach to game logic
Most infromation you can find prasing DOD focuses too much on its benefits to game performance with huge worlds. I would safely say that this actually shouldn't be the main thing DOD should be recognized by, there is even more important benefit of thinking in terms of data and data processing rather than communication between objects (OOP) - avoiding common pitfalls when trying to model game systems around Real World Things TM.
All game is, is just a bunch of data in memory that gets transformed from one form to another.
When we realize that, we can better and easier solve problems by designing data and game logic that works best together and do as less work as possible, instead of adding lots of usually unnecessary abstraction layers just because we might have a false intuition that representing data and logic in human-readable real world representation somehow will make us understand it better and quicker, while after year of development on each game, we find ourselves struggling with adding, removing or just changing how certain parts of game logic works, slapping hacky solutions here and there just to meet deadlines (we can't remove deadlines but we can reduce complexity).
We all have been through that, and we all promise ourselves we are not gonna make same mistakes again in next project, we just need to "better design how our objects communicate", while making totally new and even more problematic mistakes along the way - we can all agree that this next project is never actually free from mistakes we did last time, they all share same source.
That being said DOD isn't a miracle cure that solves all our problems, it brings its own to the table.
But all these problems revolve around us still trying to slap our (false) intuition about objects communication into problems that do not require these in the first place, us not being able to easily switch our brains from thinking in terms of object communication into thinking in terms of data transformations.
Yes, this takes time to appreciate data, but it pays off eventually with us being able to solve problems right way, not creating workarounds here ant there just to follow tons of abstraction layers requirements.
I hope this book will do its best to speed up switching from OOP to more DOD approach, by showing how to simplify problems without using tons of design patterns.
Flat and deterministic flow of data
Events-based logic approach commonly used in Unreal Engine is great for triggering foreign actions (a.k.a. objects messaging) but has one big flaw - it's not scalable as project grows. This happens because user have very limited control over order of actions happening when using events, and it grows into even bigger problem as dependency graph between parts of event-based logic grows.
Dependency graph in events-based game logic is a set of hidden rules of what action requires what other action to run before and after this one, to make sure objects being communicated have valid and expected state at each point of action execution. Hidden, because these rules emerge over time without user noticing them.
Basically any situation where feature A to run properly, depends on feature B to have a certain data, but that data is provided by feature C and so on, and there is no clear way to determine the order of actions that prepare data for any other feature in all code points related to this problem.
Sometimes it gets even worse when features triggers events that receivers triggers their events and their receivers triggers their events and so on - you can see where i'm going with it, this quickly turns into The Fuck Cascade TM of tons of state recalculation along the way in a single point in time, just to keep all objects always have valid state that depends on other game features state. This is clearly unmanageable and requires a lot of refactoring to either remove dependencies between them or to defer operations in time. This is something user usually do not put much thought into, until a really weird bug happen in some place unrelated to the problem.
Ok, so what we can do about it?
Well, flatten dependency graph and put execution of game features in line as much as possible. With that we reduce cognitive complexity to bare minimum, and user can easily manage exact order of actions to make sure state is always valid and do not need to recalculate on each change causing cascade of instant changes in features that depend on it - TL;DR: user can see exact flow of data in one place and that makes it easy to reason about, no need for juggling around the whole codebase.
In case user still wants to send data with events, but keep deterministic events execution, Systems Architecture provides feature called channels which uses polling to get received messages and execute them at the exact moment system needs to, avoiding immediate message execution when broadcasting messages.
There is also another benefit we gain from flat dependency graph, that we will discuss next:
Orthogonal and compact game logic
Quote from Eric Steven Raymond in Art of UNIX programming:
Orthogonality is one of the most important properties that can help make even complex designs compact. In a purely orthogonal design, operations do not have side effects; each action (whether it's an API call, a macro invocation, or a language operation) changes just one thing without affecting others. There is one and only one way to change each property of whatever system you are controlling.
What intrests us game developers from this quote is that when we express game logic in a set of simple work units we can easier reduce (or even remove) dependencies between parts of game logic and that allows us to easily reason about them (small code) and add/change/remove them without much time spend on refactoring (next to none dependencies between game logic parts).
Orthogonal game systems are ones that do not require each other to work (like you remove one and your game still runs), but they together make up bigger feature.
How Systems Architecture helps to achieve that?
Well, it helps by forcing user to think about data transformations instead of object communication. When we do not have to deal with abstractions, what's left is data and how we process that data. That allows to focus on solving actual problem instead of trying to fit it into some design pattern.
Because systems are all about data transformations, and System Architecture provides an ergonomic way for performing these (such as queries over game world and iterators), each system by the nature of data it works on, will keep itself doing only the work it needs and not causing hidden cascades of changes in the world, making every change explicit and intentional.
Simple example would be: instead of having per unit movement that handles input, position change and physics at once, you create separate systems:
-
input system that collects player input and turns it into input data stored either in a global resource or in some actor component, it depends on what you want to do with this data later.
You should always design your data around what it's gonna be used for, not the other way around!
-
velocity resolve system that reads input data available for given actor and converts these inputs into velocity change and stores it in velocity component.
-
position change system that reads velocity and applies it to unit position.
You might think: "ok, but this particular example seems to be an easy task that can be just executed all in one place" - and you couldn't be wrong more.
Look at the bigger picture - few months later you'll need to extend or replace physics, or you would need to add another physics resolvers that work on unit velocity and at that point this code gets bigger. You obviously split it into smaller methods, but now designers wants you to let them parameterize or even disable some of its parts at will - well, hello Refactor Tractor TM my old friend. If you split these into smaller batched work units, you gain both easy way to toggle each stage of movement and even be able to quickly change or even remove/replace with brand new ones, not to mention being able to quickly add parameterization for designers in matter of minutes to hour, not days.
Like i said before:
Keeping systems small makes it easy to reason about them and that is a one huge productivity booster that you'll love to still have after year of development on your game.
Modularity and reusability
Modularity, which means we can build complex features from smaller building blocks, gives us an ability to reuse or even share these modules with other developers (free or paid, on github or marketplace). In companies it is even more important to share and reuse certain modules between different teams and products, as demand for making more by doing less grows as company structure and portfolio grows.
Systems Architecture, by its design, allows to put generalized systems and components into separate modules, with none (or sometimes next to none) dependencies between other parts of the game. Are you making a simulation game with NPCs wandering around on the map in one project, then in next one you need to make them too? Put these in a module or just migrate them to another project. Code that doesn't compile? Fear no more, you have got decoupled systems at the moment you've started making them, there is next to none work needed after migration, just register systems into pipeline and slap these components on actors and run the game.
For indies and Unreal Engine plugin creators there are also great news!
Systems Architecture core design, when treated as platform/framework, enables Unreal Engine plugin developers to finally create an ecosystem of plugins trully compatible with each other. Ecosystem of compatible plugins that do not require a hussle of installation steps, makes plugins more accessible for users that want game features that Just Works TM and creates opportunity for more sales for plugin devs - this is a big win for both worlds, users and creators.
And as a bonus:
System Architecture is and will always remain free under permissive licenses, for anyone, accepting reasonable contributions that improve its ergonomics, quality and performance.
Conclusions
If you should take anything from this, remember:
When making games, you're basically just processing data and you don't need fancy distraction-abstractions for that.
- Make it flat
- Make it small
- Make it compact and disconnected from other systems
Then you'll keep your sanity after year of development on a single game title.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Architecture
This section will explain in-depth how Systems Architecture exactly works and how to abuse it to solve your problems the right way.
Systems Architecture adds a database-like interface over game world to allow ergonomic queries for game world data processing.
-
Actor components should only store data
Or no data, if they are considered tags, used as markers over actors. Tags such as
UEnemyComponent
that enables this actor to be queried in systems that works on enemies.And sometimes methods, but only these that do not have side effects reaching outside of this actor component scope - the point is: it's systems job to perform game logic, actor component methods should at most contain methods that recalculate their internal state, and these are operations called in more than one place.
This rule gets invalidated for situations like actor component being a bridge to some Unreal Engine built-in components or third-party code to which handles are stored in actor components. Just make sure your own actor components do as less work as possible.
-
Systems do most if not all game logic
This means whenever you want to put some commonly used piece of logic in actor component or (gods forbid) in actor, make a system for it.
It will pay off months later by still having small orthogonal game systems with as less dependencies as possible, systems that can be easily toggled, changed or even removed/replaced - no more hours or days of careful code refactoring just because you need to change or remove some bit of game feature.
-
Your game world is just a database
Systems Architecture gives you ergonomic tools to ease work on batched sets of actor components using game world queries.
UCLASS() class BOIDS_API UBoidsMovementSystem : public USystem { GENERATED_BODY() public: virtual void Run(USystemsWorld& Systems) override; }; void UBoidsMovementSystem::Run(USystemsWorld& Systems) { Super::Run(Systems); const auto* BoidsSettings = Systems.Resource<UBoidsSettings>(); if (IsValid(BoidsSettings) == false) { return; } const auto TimeScale = BoidsSettings->TimeScale; const auto DeltaTime = Systems.GetWorld()->GetDeltaSeconds() * TimeScale; for (auto& QueryItem : Systems.Query<UVelocityComponent, UBoidComponent>()) { auto* Actor = QueryItem.Get<0>(); const auto* Velocity = QueryItem.Get<1>(); const auto Position = Actor->GetActorLocation(); Actor->SetActorLocation(Position + Velocity->Value * DeltaTime); } }
With queries you have all this system logic in one place - that greatly reduces comprehensive complexity, no more need to jump around whole codebase just to understand how system works, and we make ourselves keep systems small, doing only only the work they need and no other.
Pages
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
World
Table of contents
Explanation
USystemsWorld
is a registry that stores systems,
resources and actor components.
The first thing you do is you setup your systems world by installing systems, resources and
registering actor components that will be used in systems queries to process and do something
useful with resources and actor components.
UCLASS()
class EXAMPLE_API UExampleGameInstance : public UGameInstance
{
GENERATED_BODY()
private:
virtual void Init() override;
};
void UExampleGameInstance::Init()
{
Super::Init();
auto* Subsystem = USystemsSubsystem::Get(GetWorld());
if (IsValid(Subsystem))
{
Subsystem->AcquireSystemsWorld(FName(),
[&](auto& Systems)
{
Systems.RegisterComponent<UShiaComponent>();
Systems.InstallResource<UShiaSettings>();
Systems.InstallLambdaSystem(JustDoItSystem, FInstallSystemOptions("JustDoIt"));
});
}
}
In example above we register systems world in game instance. We can of course create systems
worlds at will without registering it into USystemsSubsystem
, this global registry
is only a more convenient way to access and automate systems world management globally.
Once per tick (when using subsystem approach) all systems gets to run in order they were installed (system installation options can provide additional hints about more precise order when working with dependencies between systems).
Queries
The whole point of Systems Architecture is to process game world in a database-like fashion, which means to perform same operations on a batched set of actor components, instead of putting that work into these objects. That makes us completely decouple data from behaviors, making it easier to add, remove and change them at will, even reuse without dealing with solving hidden dependency tree problems between seemingly unrelated parts of codebase.
const auto Count = static_cast<int>(Systems.Query<UBoidComponent>().Count());
const auto Difference = Count - EXPECTED_POPULATION_NUMBER;
if (Difference > 0)
{
for (auto& QueryItem : Systems.Query<UBoidComponent>().Take(Difference))
{
auto* Actor = QueryItem.Get<0>();
Actor->Destroy();
}
}
Queries are iterators that always yields a tuple of actor
components and can be chained with other iterators to produce more elaborated, yet easy to
reason about data transformations. For example when you do Systems.Query<A, B, C>
, values
yielded by query iterator will be of type TTuple<AActor*, A*, B*, C*>
. it's also important
to note that user should never cache query objects, rather consume queries in place.
See TQuery
.
Archetypes
Internally all actor components are stored in buckets called archetypes. Given actor archetype
is determined by its FArchetypeSignature
, which is calculated from the set of
components given actor owns. When actor registers its components, systems world searches through
already existing archetypes and if it finds one, actor gets registered in that bucket, if not
new archetype bucket gets created.
The point of storing actors in groups per archetype is to make queries iteration as fast as possible by iterating over only these actors that own all requested components in a linear fashion without need for skipping over actors that we don't need to work on.
Archetype signatures are basically bitsets of 256 bits capacity, each bit represents registered actor component. Whenever signature gets calculated for given query, it makes empty signature and enables bits for requested component, then it compares this signature with each archetype bucket signature and stores in query object a list of archetypes to iterate on.
Problems that it solves
Usually when working with game managers it's a common practice to cache managers in other managers to not perform costly searches in game world. This is unnecessary work and Systems Architecture solves that by storing resources in single systems world, easily accessible from any system without any need for caching any used resources in systems itself.
Another common problem is usually hierarchy of game logic gets growing in depth, like when game logic is performed by game objects themselves, they reference one another, growing hidden dependency tree and by that comprehensive complexity that requires sometimes hours to jump around whole codebase just to understand how data flows between certain objects. Systems Architecture forces user to make flat game logic with explicit order of execution, additionally making units of work as small and as precise as possible to make it easy to reason about how game data gets processed.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Systems
Table of contents
Explanation
Systems are small but precise units of work, work that's mainly about processing game world data using queries. These queries are processed using iterators, and that makes queries the whole point of systems ergonomics since they simplify both computational and comprehensive complexity and make that part of game logic really easy to reason about, which in turn makes adding, removing and changing game logic easy and quicker than with monolithic code or features highly dependent on each other, scattered around the codebase.
Types of systems
There are few types of systems, but the ones that you will mostly use are stateless (lambdas/functions) and stateful (classes) systems.
Stateless systems
These are the most common ones. Stateless systems are just lambdas/functions that expect reference
to USystemsWorld
to operate on it. Stateless systems do not require any internal state
to operate.
void MovementSystem(USystemsWorld& Systems)
{
const auto* Settings = Systems.Resource<USettings>();
if (IsValid(Settings) == false)
{
return;
}
const auto TimeScale = Settings->TimeScale;
const auto DeltaTime = Systems.GetWorld()->GetDeltaSeconds() * TimeScale;
for (auto& QueryItem : Systems.Query<UVelocityComponent>())
{
auto* Actor = QueryItem.Get<0>();
const auto* Velocity = QueryItem.Get<1>();
const auto Position = Actor->GetActorLocation();
Actor->SetActorLocation(Position + Velocity->Value * DeltaTime);
}
}
void UExampleGameInstance::Init()
{
Super::Init();
auto* Subsystem = USystemsSubsystem::Get(GetWorld());
if (IsValid(Subsystem))
{
Subsystem->AcquireSystemsWorld(FName(),
[&](auto& Systems)
{
Systems.RegisterComponent<UVelocityComponent>();
Systems.InstallLambdaSystem(MovementSystem, FInstallSystemOptions("Movement"));
});
}
}
Stateful systems
Stateful systems are useful in cases where system requires some internal state that cannot be shared with other systems via resources (for example internal cache that is used to track changes in the system across multiple game ticks).
UCLASS()
class EXAMPLE_API ULogBirdsNumberChangeSystem : public USystem
{
GENERATED_BODY()
public:
virtual void Run(USystemsWorld& Systems) override;
UPROPERTY()
uint32 LastCount = 0;
};
void ULogBirdsNumberChangeSystem::Run(USystemsWorld& Systems)
{
Super::Run(Systems);
if (Systems.ComponentsDidChanged<UBirdComponent>() == false)
{
return;
}
const auto Number = static_cast<int>(Systems.Query<UBirdComponent>().Count());
const Difference = Number - this->LastCount;
this->LastCount = Number;
if (Difference > 0)
{
UE_LOG(LogTemp, Warning, TEXT("Added %i birds"), Difference);
}
else if (Difference < 0)
{
UE_LOG(LogTemp, Warning, TEXT("Removed %i birds"), -Difference);
}
}
void UExampleGameInstance::Init()
{
Super::Init();
auto* Subsystem = USystemsSubsystem::Get(GetWorld());
if (IsValid(Subsystem))
{
Subsystem->AcquireSystemsWorld(FName(),
[&](auto& Systems)
{
Systems.RegisterComponent<UBirdComponent>();
Systems.InstallSystem<ULogBirdsNumberChangeSystem>(FInstallSystemOptions("LogBirdsNumberChange"));
});
}
}
Problems that it solves
There is very common (bad) practice in the industry that we make a game prototype, then we build actual game on top of that prototype codebase, instead of treating it purely as a reference for an actual, properly implemented solutions in actual project - this can and will quickly lead to poisoned codebase months and years later. It is really hard to break that habit, mostly in professional game development because time budget and famous last words such as "we will refactor that later", but that "later" do not come as soon as we would like, making us having a weak foundation and we build new features on top of that weak foundation, basically playing Jenga with our codebase, codebase which parts of will fall later as the project grows, causing The Fuck Cascade TM. and frustration later on - we can do better than that.
Systems Architecture helps with that by forcing simple and modular codebase from the get go, making switching from prototype to actual implementation a matter of just swapping prototype systems with actual properly implemented systems, sometimes not even having to change them at all.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Components
Table of contents
Explanation
See USystemsActorComponent
and USystemsSceneComponent
.
Systems Architecture treats game world as database that user can query. That enables us to perform same operations on a batch of actors and their requested components in-place.
const auto Count = static_cast<int>(Systems.Query<UBoidComponent>().Count());
const auto Difference = Count - EXPECTED_POPULATION_NUMBER;
if (Difference > 0)
{
for (auto& QueryItem : Systems.Query<UBoidComponent>().Take(Difference))
{
auto* Actor = QueryItem.Get<0>();
Actor->Destroy();
}
}
And all you have to do is to add or remove components on your actors of interest to enable/disable them for certain systems, instead of juggling or even duplicating code around many classes, growing your hidden dependency graph. That simplifies the game logic and greatly improves iteration times on game systems.
Keeping data decoupled from game logic enables you to easily perform different logic on same set of data, avoiding duplication of both logic and data, making it easier to iterate on them.
Of course sometimes keeping some logic in components is reasonable - good practice is to add into actor components only these methods that do not produce any side effects outside this component scope, which means logic that only serves to recalculate its internal state.
Problems that it solves
When working with Unreal Engine, mostly as a beginner, a little bit less as an advanced user, you find yourself slapping a lot of code into actors. The general thought wandering around your mind at that time usually goes like this: "This code is something that only this actor does, so it's obvious that this code belongs to this actor class, duh" - at that time, while there is still not much work put in there you might be right, but in that case your intuition is wrong once again.
Across next months you slap bits of logic there and its complexity grows. It doesn't help that you've split it into several methods, all of that effort gets invalidated once you have to add a second, and a third actor that does similar things, and you suddenly find yourself redesigning proper decoupling or inheritance tree or even doing massive refactoring because this one problem touches many seemingly unrelated places, that were part of complex hidden dependency tree.
All of that could be avoided with splitting monolithic work per components, but we can do even better and completely decouple data from logic that we put in small dedicated systems.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Resources
Table of contents
Explanation
Resource in context of Systems Architecture is any global object that all systems should be able to access (though resources definitely aren't singletons! - they share their lifetime with owning systems world). Objects such as:
-
Configuration/settings data
For example systems run criteria that tells the conditions under what certain systems can run. Or Data Assets / Data Tables passed from game instance to systems world.
-
Cached data shared between systems
Do you have spatial partitioning system that builds RTree for world actors to speed up spatial queries, and other systems in need to find the closest actor to their location? You definitely put that data in a resource object and make other systems query and read that resource!
-
Game managers
Common Unreal Engine game development pattern is to create work units called "managers" that manage certain state. For some solutions, especially when dealing with third-party plugins, we have to still have access into these managers in our systems - definitely put them in systems world resources.
UCLASS(BlueprintType)
class EXAMPLE_API USettings : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
float TimeScale = 1;
};
void UExampleGameInstance::Init()
{
Super::Init();
auto* Subsystem = USystemsSubsystem::Get(GetWorld());
if (IsValid(Subsystem))
{
Subsystem->AcquireSystemsWorld(FName(),
[&](auto& Systems)
{
Systems.RegisterComponent<UVelocityComponent>();
// USettings asset set up in editor.
Systems.InstallResourceRaw(this->Settings);
Systems.InstallLambdaSystem(MovementSystem, FInstallSystemOptions("Movement"));
});
}
}
void MovementSystem(USystemsWorld& Systems)
{
const auto* Settings = Systems.Resource<USettings>();
if (IsValid(Settings) == false)
{
return;
}
const auto TimeScale = Settings->TimeScale;
const auto DeltaTime = Systems.GetWorld()->GetDeltaSeconds() * TimeScale;
for (auto& QueryItem : Systems.Query<UVelocityComponent>())
{
auto* Actor = QueryItem.Get<0>();
const auto* Velocity = QueryItem.Get<1>();
const auto Position = Actor->GetActorLocation();
Actor->SetActorLocation(Position + Velocity->Value * DeltaTime);
}
}
Proxy Resources
Along the journey you might find yourself in a need for having to use wrapper-like resources that themselves are not unique types but they wrap unique type resource - wrappers/containers for some inner data. For that scenario you will use concept called Proxy Resources, which allows you to install resource wrapper, which of type itself is not used, instead you provide its inner type used for queries.
First we define proxy resource:
UCLASS()
class EXAMPLE_API UInventoryWrapper : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY()
UInventory* GeneratedInventory = nullptr;
};
Then we install it in Systems World:
auto* Wrapper = NewObject<UInventoryWrapper>(Systems, UInventoryWrapper::StaticClass());
Systems.InstallProxyResource<UInventory>(Wrapper, [](auto* Wrapper) { return Wrapper->GeneratedInventory; });
And finally we access it:
Systems.ProxyResource<UInventory>()->AddItem(FItem{FItemType::Sword});
Problems that it solves
While in most cases all what you do is you process game world queries, sometimes you require to access some configuration data, or cache some data for use by many other systems, or you just need to interact with third-party game managers - these are useful use cases and Systems Architecture doesn't get rid of them, rather make their use more ergonomic, yet not giving them global lifetime like with dangerous singletons.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Subsystem
Table of contents
Explanation
For the sake of convenience Systems Architecture provides handy wrapper over
USystemsWorld
global instances as a USystemsSubsystem
that is basically a registry of systems worlds accessible from any place in the code.
All user needs to do is to register and setup systems world using
USystemsSubsystem::AcquireSystemsWorld
when certain game phase starts
(either game, level, menu, etc) before adding/removing/querying actor components, and
unregister systems world using USystemsSubsystem::ReleaseSystemsWorld
when given game phase ends.
Examples
The most common game phases where systems might exists are game instance and game mode.
- You register systems world in game instance when there are systems are possible to run during entire game lifetime. This is the easiest and safest option.
- You register systems world in game mode when there are systems that are possible to run during given game level lifetime. This is useful if we for example have completely separate pipelines set for each level or game mode.
Game instance
Since game instance has a lifetime of entire game run, we only care here about
registering systems world to subsystem on UGameInstance::Init
.
UCLASS()
class EXAMPLE_API UExampleGameInstance : public UGameInstance
{
GENERATED_BODY()
private:
virtual void Init() override;
};
void UExampleGameInstance::Init()
{
Super::Init();
auto* Subsystem = USystemsSubsystem::Get(GetWorld());
if (IsValid(Subsystem))
{
Subsystem->AcquireSystemsWorld(FName(),
[&](auto& Systems)
{
Systems.RegisterComponent<UShiaComponent>();
Systems.InstallResource<UShiaSettings>();
Systems.InstallLambdaSystem(JustDoItSystem, FInstallSystemOptions("JustDoIt"));
});
}
}
Game mode
When it comes to game mode, we should register systems world on AGameModeBase::InitGame
(it runs before any actor BeginPlay
- if we register it on BeginPlay
, then some
actors placed on level might try to register to yet non-existing systems world) and
unregister it on AGameModeBase::EndPlay
.
UCLASS()
class EXAMPLE_API AExampleGameMode : public AGameModeBase
{
GENERATED_BODY()
private:
virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
};
void AExampleGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage)
{
Super::InitGame(MapName, Options, ErrorMessage);
auto* Subsystem = USystemsSubsystem::Get(GetWorld());
if (IsValid(Subsystem))
{
Subsystem->AcquireSystemsWorld(FName(),
[&](auto& Systems)
{
Systems.RegisterComponent<UShiaComponent>();
Systems.InstallResource<UShiaSettings>();
Systems.InstallLambdaSystem(JustDoItSystem, FInstallSystemOptions("JustDoIt"));
});
}
}
void AExampleGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
auto* Subsystem = USystemsSubsystem::Get(GetWorld());
if (IsValid(Subsystem))
{
Subsystem->ReleaseSystemsWorld(ThisClass::SYSTEMS_WORLD);
}
}
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Pipelines
Table of contents
Explanation and setup
See USystemsPipeline
.
Systems pipeline is a data asset that describes configuration of a single systems world:
its components, resources and systems that should be installed for new systems world.
Given pipeline is then assigned to either ASystemsGameMode
or
ASystemsGameState
to automatically create and destroy dedicated systems world.
Having systems pipeline defined in a data asset simplifies systems world setup and gives more flexibility to game designers so they can balance and test special cases and new game features without asking programmers to take their time and make changes to the setup.
World Id
- ID under which this pipeline systems world will be registered globally. Usually there is gonna be single systems world so it's best to leaveNone
here, but in rare cases there might be sometimes a need to for multiple systems worlds in the game at once so here user can specify that.
Components
See components architecture book page.
List of all component types that are gonna be recognized by this systems world.
Development Build Only
- Ensures this component will be registered in development builds only (and editor builds).Use
- Useful for quickly disabling given component from the pipeline without actually removing it, can be used for quick dirty tests.
Resources
See resources architecture book page.
Type resources
List of type resources to install.
Type resources are usually just a typical UObject
-based
game managers, 3rd-party library wrappers, data shared runtime data cache/storage and such.
Use Global Storage
- Makes this resource transferable from one pipeline to another, to achieve shared resources. For example if main menu pipeline has User Info resource to fill its user name, game pipeline will obtain exactly the same resource instead of creating new one, to use previously filled user name in the game.Development Build Only
- Ensures this resource will be registered in development builds only (and editor builds).Use
- Useful for quickly disabling given resource from the pipeline without actually removing it, can be used for quick dirty tests.
Asset resources
List of asset resources to install.
Asset resources are purely data (either baked data sources or systems settings) that will be used by systems.
Proxy
- Marks this resource as proxy with custom inner resource unpackerUSystemsPipelineProxyResource
.Development Build Only
- Ensures this resource will be registered in development builds only (and editor builds).Use
- Useful for quickly disabling given resource from the pipeline without actually removing it, can be used for quick dirty tests.
Systems
See systems architecture book page.
Here we list all of the systems that are gonna be used by this pipeline's systems world.
Order of systems in the list represents order of their execution!
Template
- TemplateUSystem
-based object that can be configured in-place.Development Build Only
- Ensures this system will be registered in development builds only (and editor builds).Use
- Useful for quickly disabling given system from the pipeline without actually removing it, can be used for quick dirty tests.
Note the difference between category of systems:
Startup
- Runs only at systems world installation.Persistent
- Runs on every game frame.Cleanup
- Runs only at systems world uninstallation.
Commonly used template types:
Stateful systems
Stateful systems are ones that can have configuration properties put in them and/or can hold
internal state (for example to cache some values between next system runs, change detection
for example). User makes them by inheriting directly from USystem
class.
Stateless systems
Stateless systems are basically lambdas/functions registered into FSystemsReflection
.
The reason for lambda systems to be registered is that Unreal doesn't come with it's own global
function reflection so we have to put these into a dedicated registry ourselves, so we can assign
them in ULambdaSystem
's Function Name
property.
Every game or plugin module that exposes its lambda systems, has to register them in module
StartupModule
and ShutdownModule
methods like this:
#include "Tutorial.h"
#include "Systems/Public/SystemsReflection.h"
#include "Tutorial/Systems/Persistent/TutorialGoToSystem.h"
#include "Tutorial/Systems/Persistent/TutorialMoveTowardsTargetSystem.h"
#include "Tutorial/Systems/Persistent/TutorialMovementSystem.h"
#include "Tutorial/Systems/Persistent/TutorialSelectActorsSystem.h"
#define LOCTEXT_NAMESPACE "FTutorialModule"
#define SYSTEMS_NAMESPACE "Tutorial"
void FTutorialModule::StartupModule()
{
REGISTER_SYSTEM_FUNCTION(TutorialGoToSystem);
REGISTER_SYSTEM_FUNCTION(TutorialMoveTowardsTargetSystem);
REGISTER_SYSTEM_FUNCTION(TutorialMovementSystem);
REGISTER_SYSTEM_FUNCTION(TutorialSelectActorsSystem);
}
void FTutorialModule::ShutdownModule()
{
UNREGISTER_SYSTEM_FUNCTION(TutorialGoToSystem);
UNREGISTER_SYSTEM_FUNCTION(TutorialMoveTowardsTargetSystem);
UNREGISTER_SYSTEM_FUNCTION(TutorialMovementSystem);
UNREGISTER_SYSTEM_FUNCTION(TutorialSelectActorsSystem);
}
#undef LOCTEXT_NAMESPACE
#undef SYSTEMS_NAMESPACE
IMPLEMENT_GAME_MODULE(FTutorialModule, Tutorial);
Blueprint systems
Blueprint systems are systems made for quick and dirty prototyping by technical designers and content creators, they serve purely as final reference for programmers to translate them into native C++ systems. They exists exactly for scenarios where technical designer or content creator has an idea for a game feature they want to quickly make and test its viability, without bothering programmers to put their hands into every aspect of it, making feature design phase much faster and removing every back-and-forth between programmers and idea owners.
Blueprint systems are made up of two objects:
- Query blueprint with public properties that represents what components (and optionally actor) query expects to iterate on.
- System blueprint with
OnRun
event that performs one or more queries on systems world.
Here is example of creating blueprint systems:
-
Create new game object that inherits from
UObject
:And name it
BP_ShakeSystemQuery
. -
Create public properties of actor/scene component types (and optionally an actor) your system query wants to iterate on:
-
Create new game object that inherits from
UScriptableSystem
:And name it
BP_ShakeSystem
. -
Create
System World Query
node and assignBP_ShakeSystemQuery
object class:With this we tell tis query iterator it will yield
BP_ShakeSystemQuery
objects with requested components and actor assigned to its fields. -
Create
Lazy Iterate
node:Since
Query
returns a lazy iterator object (you can read about lazy iterators in this architecture book page), this node is aFor Each
equivalent, to iterate over this iterator elements. -
Apply changes to actor offset component we got from yielded query object:
Using pipeline data assets
While you can call USystemsPipeline::Install
and USystemsPipeline::Uninstall
directly to install and uninstall given pipeline systems world globally, it is useful to have that automated,
and for that these are game objects that do this for you, based on pipeline data asset assigned to them
in the editor:
Systems game mode
See ASystemsGameMode
.
Consider using it to install pipeline that has to run singleplayer or multiplayer server game logic (since game modes are spawned only there).
-
Create game mode that inerits from
ASystemsGameMode
:And name it
BP_TutorialGameMode
. -
Assign
USystemsPipeline
data asset intoSystems Pipeline
property:
Systems game state
See ASystemsGameState
.
Consider using it to install pipeline that has to run multiplayer client game logic (internally it will enforce to install only on multiplayer client).
-
Create game state that inerits from
ASystemsGameState
:And name it
BP_TutorialGameState
. -
Assign
USystemsPipeline
data asset intoSystems Pipeline
property: -
Additionally you assign this game state class in its sibling game mode:
To make server game mode spawn this game state with its pipeline on clients.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Iterators
Table of contents
Explanation
Iterators, or more precisely lazy iterators, are an useful tool for more efficient and ergonomic processing of collections and procedural generators. Lazy iterators are lazy because on their own they do not yield any values until consumed.
// [0.0, 4.0, 16.0, 36.0, 64.0]
const TArray<float> Result = IterRange(0, 10)
.Filter([](const auto& Value) { return Value % 2 == 0; })
.Map<float>([](const auto& Value) { return static_cast<float>(Value * Value); })
.CollectArray();
This lazyness allows us to produce more elaborated processing over data without making unnecessary caching between iteration stages, and that also makes iteration code cleaner and easier to reason about.
Types of iterators
-
Consumers
Consumers are final iterators that consume yielded values and produce some result out of it, usually single value.
// 5 const TOptional<int> Result = IterRange(0, 10).Find([](const auto& Value) { return Value >= 5; });
-
Colectors
Collectors are special kind of consumer iterators that are an explicit way to collect all yielded values into commonly used collections such as
TArray
,TSet
andTMap
.const TArray<int> Result = IterRange(0, 10).CollectArray();
TQuery::CollectArray
TQuery::CollectIntoArray
TQuery::CollectSet
TQuery::CollectIntoSet
IterCollectMap
IterCollectIntoMap
-
Converters
Converters are stages of iteration that converts values yielded by previous iteration stages into some new values.
// [0.0, 4.0, 16.0, 36.0, 64.0] const TArray<float> Result = IterRange(0, 10) .Filter([](const auto& Value) { return Value % 2 == 0; }) .Map<float>([](const auto& Value) { return static_cast<float>(Value * Value); }) .CollectArray();
Creating custom iterators
While built-in iterators provided by SystemsArchitecture should be enough for common usecases, sometimes for more advanced solutions you might find yourself in a need of creating a custom iterator that cannot be solved using built-in ones.
Anatomy if iterators
Let's take a look at Repeat iterator:
#pragma once
#include "CoreMinimal.h"
// Make sure to include converters declaration header first.
#include "Systems/Public/Iterator/ConvertersDecl.h"
// Then converters, macros and size hint, order doesn't matter here.
#include "Systems/Public/Iterator/Converters.h"
#include "Systems/Public/Iterator/Macros.h"
#include "Systems/Public/Iterator/SizeHint.h"
// This iterator is generalized over type of values it yields.
template <typename T>
struct TIterRepeat
{
public:
// We need to provide type aliases for iterators injected with macros will
// properly understand this iterato when wrapping it internally.
using Self = TIterRepeat<T>;
using Item = typename T;
TIterRepeat() : Value(TOptional<T>())
{
}
TIterRepeat(T Data) : Value(TOptional<T>(Data))
{
}
// Provide `Next` method that returns optional value of item type.
TOptional<Item> Next()
{
return TOptional<T>(this->Value);
}
// Provide `Sizehint` method thar returns hint about estimated range of
// items it can yield.
IterSizeHint SizeHint() const
{
return IterSizeHint{0, TOptional<uint32>()};
}
private:
TOptional<T> Value = {};
public:
// To make iterators chains we call special macro that injects all other
// built-in iterators as this one methods.
ITER_IMPL
};
// Provide handy construction function for ergonomics, just because of C++
// having easier times with types deduction on them.
template <typename T>
TIterRepeat<T> IterRepeat(T Value)
{
return TIterRepeat<T>(Value);
}
-
Self
type aliasIterators chaining is done by wrapping consecutive iterators in one another so when consumer calls
Next
, it internally callsNext
of iterator it wraps, and so on. To make them easily injectable with macros, they useSelf
as an alias for their type. -
Item
type aliasType alias for type of value that this iterator yields. Not stores, not takes as an input - exactly one that it yields. This is again used for iterators wrapping purposes so converter iterators that wraps other iterators can identify value type of previous one in chain by
typename ITERATOR::Item
. -
Next
methodEvery iterator should implement
TOptional<Item> Next()
method. This method does the actual job of yielding a value.See
TQuery::Next
. -
SizeHint
method.Size hints are used by for example collector iterators to estimate the capacity of collection where yielded data will be stored. This at most removes reallocations when adding every next value into that collection, and at least reduces it to some reasonable number.
Finite iterators will give lower bounds and upper bounds set, infinite iterators will give only lower bounds.
See
TQuery::SizeHint
,IterSizeHint
. -
ITER_IMPL
macroThis macro injects other iterators as methods of this one. Without it user would be left with ugly iterators chains made by passing next stages as argument to iterator functions/constructors.
Iterator adapters
In some rare cases you might find yourself struggling to express your data processing with built-in iterators, or you must to make some optimizations that cannot be solved using provided ones. For this advanced usecase you can create custom iterator adapters that works basically the same way as converter iterators.
template <typename T>
struct TIterOddAdapter
{
public:
template <typename I>
TOptional<T> Next(I& Iter)
{
Iter.Next();
return Iter.Next();
}
template <typename I>
IterSizeHint SizeHint(const I& Iter) const
{
return Iter.SizeHint();
}
};
// [1, 3, 5, 7, 9]
const TArray<int> Result = IterRange(0, 10).Adapt(TIterOddAdapter<int>()).CollectArray();
They differ from regular iterators in a way that they do not need type aliases and their Next
and
SizeHint
methods require reference to previous stage iterator so they both consume and process its
yielded values.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Channels
Table of contents
Explanation
See TSenderChannel
, TReceiverChannel
.
Shared channels are Single-Produce, Multiple-Consumer events that instead being executed at the time of sending message (pushing), they are executed at the time of receiving, which is an on-demand operation - user decides when to read messages received in event.
auto Sender = TSenderChannel<int>();
auto Receiver = Sender.Receiver(1);
Sender.Send(42);
while (const auto Value = Receiver.Receive())
{
UE_LOG(LogTemp, Warning, TEXT("Received value: %i"), Value);
}
Internally channels work as a ring buffer behind thread-safe shared pointer in receiver channel, and sender channel only holds a weak pointer to list of registered receivers. When you want to subscribe to a channel, you create a receiver and store that receiver somewhere.
It's important to note that receiver is bound to a sender channel as long as it exists, so there is no need for intentional unbinds, it is all automated by its design.
After you have sender and receivers made, when you tell sender channel to send message, it goes through all weak pointers pointing to receiver channels, and write cloned data into each of their queues. Messages are stored, but not yet executed.
When you want to receive and execute a message, just make receiver read sequence of next messages in queue and do something useful with them. That's all, simple isn't it?
Problems that it solves
There is only one but big problem that it solves - making more deterministic and ordered data flow between parts of game logic. That way we avoid situations where objects communicating with each other are dependend on proper order of messages being sent and received but we can't ensure that order. We also avoid event cycles, where there is set of event points that suddenly gets connected in a way that introduces a cycle of passing messages, leading to game freezes/crashes due to stack overflow, or simply just situations where some data gets passed too early and invalidates dependent object state causing unnecessary state recalculations later by other objects trying to get some data out of it.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Learning materials
List of learning materials that showcase common and special case usage of Systems Architecture.
Pages
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Tutorial
Here we will showcase step-by-step process of basic usage of Systems Architecture - both for C++ and Blueprint usage scenarios.
Pages
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Installation
Until plugin gets accepted to Unreal Engine Marketplace, we need to install plugin from GitHub.
-
Download plugin from GitHub Release page (core plugin is in
Systems-Architecture.zip
archive file). -
Make sure project folder structure contains
Plugins
folder in its root (next toContent
andSource
folders), then unzip its content intoPlugins
folder. -
Launch editor and confirm plugin is properly added by trying to create new data asset of
USystemsPipeline
type:
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Setup component
C++
-
Tutorial/Components/TutorialMovementComponent.h
:#pragma once #include "CoreMinimal.h" #include "Systems/Public/SystemsComponent.h" #include "TutorialMovementComponent.generated.h" UCLASS(BlueprintType, Blueprintable, Meta = (BlueprintSpawnableComponent)) class TUTORIAL_API UTutorialMovementComponent : public USystemsActorComponent { GENERATED_BODY() public: UPROPERTY(EditAnywhere, BlueprintReadWrite) FVector Value = FVector(0); };
-
Tutorial/Components/TutorialMovementComponent.cpp
:#include "Tutorial/Components/TutorialMovementComponent.h"
Blueprint
-
Create new game object that inherits from
USystemsActorComponent
:And name it
BP_TutorialMovement
. -
Add
Value
property ofVector
type:
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Setup actor
-
Create new game object that inherits from
ASystemsActor
orActor
(if you don't need automatic built-in engine components registration based onSystems
tag) - same for pawns (ASystemsPawn
orPawn
):And name it
BP_Entity
. -
Add
BP_TutorialMovement
component: -
Assign random unit vector to
TutorialMovement
::Value
onBeginPlay
event:To initially pick a random direction for this actor further movement.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Setup system
C++
-
Tutorial/Systems/TutorialMovementSystem.h
:#pragma once #include "CoreMinimal.h" class USystemsWorld; UFUNCTION() void TUTORIAL_API TutorialMovementSystem(USystemsWorld& Systems);
-
Tutorial/Systems/TutorialMovementSystem.cpp
:#include "Tutorial/Systems/Persistent/TutorialMovementSystem.h" #include "Systems/Public/SystemsWorld.h" #include "Tutorial/Components/TutorialMovementComponent.h" void TutorialMovementSystem(USystemsWorld& Systems) { const auto DeltaTime = Systems.GetWorld()->GetDeltaSeconds(); Systems.Query<UTutorialMovementComponent>().ForEach( [&](auto& QueryItem) { auto* Actor = QueryItem.Get<0>(); auto* Movement = QueryItem.Get<1>(); const auto Position = Actor->GetActorLocation(); const auto Velocity = Movement->Value * DeltaTime; Actor->SetActorLocation(Position + Velocity); }); }
Since this is a lambda system, we need to register it to FSystemsReflection
.
To do that you have to register it in your game module:
-
Tutorial/Tutorial.h
:#pragma once #include "CoreMinimal.h" #include "Modules/ModuleManager.h" class FTutorialModule : public IModuleInterface { public: virtual void StartupModule() override; virtual void ShutdownModule() override; };
-
Tutorial/Tutorial.cpp
:#include "Tutorial.h" #include "Systems/Public/SystemsReflection.h" #include "Tutorial/Systems/Persistent/TutorialMovementSystem.h" #define LOCTEXT_NAMESPACE "FTutorialModule" #define SYSTEMS_NAMESPACE "Tutorial" void FTutorialModule::StartupModule() { REGISTER_SYSTEM_FUNCTION(TutorialMovementSystem); } void FTutorialModule::ShutdownModule() { UNREGISTER_SYSTEM_FUNCTION(TutorialMovementSystem); } #undef LOCTEXT_NAMESPACE #undef SYSTEMS_NAMESPACE IMPLEMENT_GAME_MODULE(FTutorialModule, Tutorial);
Blueprint
-
Create new game object that inherits from
Object
:And name it
BP_TutorialMovementQuery
. -
Add
BP_TutorialMovement
component and actor proeprties to tell over what systems world elements this query wants to iterate over.Here Actor is
Actor
andMovement
isBP_TutorialMovement
component. -
Create new game object that inherits from
UScriptableSystem
:And name it
BP_TutorialMovementSystem
. -
Override
On Run
event: -
Create
Query
node and assignBP_TutorialMovementQuery
class to it: -
Create
Lazy Iterate
node: -
Perform update on yielded object:
Note that
Movement
::Value
is multiplied byDelta Time
provided byOn Run
event.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Setup game
-
Create new data asset that inherits from
USystemsPipeline
:And name it
DA_TutorialPipeline
. -
Add components:
-
C++
-
Blueprint
-
-
Add persistent systems:
-
C++
-
Blueprint
-
-
Create new game object that inherits from
ASystemsGameMode
:And name it
BP_TutorialGameMode
. -
Assign
BP_TutorialPipeline
toSystems Pipeline
property: -
Assign
BP_TutorialGameMode
to level you want this pipeline to run on: -
Put some of
BP_Entity
actors on level, run game inPIE
and see systems performing!
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
C++ API Reference
Structs
FActorsIter
FArchetypeSignature
FInstallSystemOptions
FMetronome
FSystemsPipelineAssetResource
FSystemsPipelineComponent
FSystemsPipelineResource
FSystemsPipelineSystem
FSystemsPipelineTypeResource
IterSizeHint
TQuery
TReceiverChannel
TResult
TSenderChannel
TTaggedQuery
Classes
ASystemsActor
ASystemsGameMode
ASystemsGameState
ASystemsPawn
FSystemsReflection
UDynamicIterator
UDynamicQuery
ULambdaSystem
UScriptableSystem
USystem
USystemsActorComponent
USystemsGroupComponent
USystemsPipeline
USystemsReflection
USystemsSceneComponent
USystemsStatics
USystemsSubsystem
USystemsWorld
Functions
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Structs
FActorsIter
FArchetypeSignature
FInstallSystemOptions
FMetronome
FSystemsPipelineAssetResource
FSystemsPipelineComponent
FSystemsPipelineResource
FSystemsPipelineSystem
FSystemsPipelineTypeResource
IterSizeHint
TQuery
TReceiverChannel
TResult
TSenderChannel
TTaggedQuery
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: FActorsIter
struct SYSTEMS_API FActorsIter;
Reflection-enabled
Specifiers:
- BlueprintType
Query iterator over systems world actors.
Methods
-
Adapt
public: template <typename ADAPTER> TIterAdapt<Self, ADAPTER> Adapt( ADAPTER Adapter );
Injects custom iterator adapter into the chain of iteration.
Useful only for really custom/advanced solutions that cannot be solved with regular iterators.
Note
ADAPTER
should implement iterator adapter methods. Yielded values share same type wit iterator that wraps this adapter.Example
// [1, 3, 5, 7, 9] const TArray<int> Result = IterRange(0, 10).Adapt(TIterOddAdapter<int>()).CollectArray();
template <typename T> struct TIterOddAdapter { public: template <typename I> TOptional<T> Next(I& Iter) { Iter.Next(); return Iter.Next(); } template <typename I> IterSizeHint SizeHint(const I& Iter) const { return Iter.SizeHint(); } };
Arguments
-
Adapter
ADAPTER Adapter
-
-
All
public: template <typename FUNCTOR> bool All( FUNCTOR Func );
Checks if all values yielded by this iterator passes predicate.
Note
FUNCTOR
should comply to given function signature:bool(I::Item Value)
whereValue
holds current value yielded by iterator.Example
// false const bool Result = IterRange(0, 10).All([](const auto& Value) { return Value > 5; });
Arguments
-
Func
FUNCTOR Func
-
-
Any
public: template <typename FUNCTOR> bool Any( FUNCTOR Func );
Checks if any value yielded by this iterator passes predicate.
Note
FUNCTOR
should comply to given function signature:bool(I::Item Value)
whereValue
holds current value yielded by iterator.Example
// true const bool Result = IterRange(0, 10).Any([](const auto& Value) { return Value > 5; });
Arguments
-
Func
FUNCTOR Func
-
-
Cast
public: template <typename RESULT> TIterCast<typename RESULT, Self> Cast();
Casts yielded values to another type.
Commonly used as a shourtcut for mapping between types using target type constructor.
Note
RESULT
is type that this iterator will yield after casting.Example
// [0.0, 1.0, 2.0, 3.0, 4.0] const TArray<float> Result = IterRange(0, 5).Cast<float>().CollectArray();
-
Chain
public: template <typename ITER> TIterChain<Self, ITER> Chain( ITER&& Iter );
Appends another iterator at the end of this iterator.
Useful for combining results of different iterators that yield same value type.
Note
ITER
should implement iterator methods. Yielded values share same type with this iterato value type.Example
// [0, 1, 2, 3, 4, -5, -4, -3, -2, -1] const TArray<int> Result = IterRange(0, 5).Chain(IterRange(-5, 0)).CollectArray();
Arguments
-
Iter
ITER&& Iter
-
-
ComparedBy
public: template <typename FUNCTOR> TOptional<Item> ComparedBy( FUNCTOR Func );
Finds iterator value that compared to other items gets greater "score".
Headline is rather vague, but what it actually does is user can do finding min/max value with this iterator consumer.
Note
FUNCTOR
should comply to given function signature:bool(I::Item A, I::Item B)
whereA
holds current value yielded by iterator andB
is the one that has best "score" so far.Example
// 42 const int Result = IterRange(0, 42).ComparedBy([](const auto A, const auto B) { return A > B; });
Arguments
-
Func
FUNCTOR Func
-
-
Count
public: uint32 Count();
Returns exact number of items that iterator can yield.
Example
// 10 const uint32 Result = IterRange(0, 10).Count();
-
Enumerate
public: TIterEnumerate<Self> Enumerate();
Enumerates values in this iterator.
Useful for reading index of element/iteration.
Note
Yielded values have type:
TTuple<uint32, Item>
, which means this iterator yields tuple o index-value pair.Example
// [0, 1, 2, 3, 4] const TArray<int> Result = IterRepeat(42).Enumerate().Map<int>([](const auto& Pair) { return Pair.Get<0>(); }).Take(5).CollectArray();
-
FActorsIter
public: FActorsIter( USystemsWorld* Systems );
Constructs query from systems.
An equivalent of calling
USystemsWorld::Actors
Arguments
-
Systems
USystemsWorld* Systems
Pointer to systems world of which actors user wants to iterate on.
-
-
Filter
public: template <typename FUNCTOR> TIterFilter<Self, typename FUNCTOR> Filter( FUNCTOR Func );
-
FilterMap
public: template <typename RESULT, typename FUNCTOR> TIterFilterMap<typename RESULT, Self, typename FUNCTOR> FilterMap( FUNCTOR Func );
Filters values in the iterator by predicate and maps them to another type.
Note
RESULT
is a type of values yielded by this iterator that maps into.FUNCTOR
should comply to this function signature:TOptional<RESULT>(const I::Item& Value)
Returning some value means pasing it to next iterator, returning none means we omit thi value.Example
// [0.0, 2.0, 4.0, 6.0, 8.0] const TArray<float> I = IterRange(0, 10) .FilterMap<float>( [](const auto& Value) { if (Value % 2 == 0) { return TOptional<float>(static_cast<float>(Value)); } else { return TOptional<float>(); } }) .CollectArray();
Arguments
-
Func
FUNCTOR Func
-
-
Find
public: template <typename FUNCTOR> TOptional<Item> Find( FUNCTOR Func );
Finds and returns value in this iterator that passes
FUNCTOR
predicate.Note
FUNCTOR
should comply to given function signature:bool(I::Item Value)
whereValue
holds current value yielded by iterator.Example
// 5 const TOptional<int> Result = IterRange(0, 10).Find([](const auto& Value) { return Value >= 5; });
Arguments
-
Func
FUNCTOR Func
-
-
FindMap
public: template <typename RESULT, typename FUNCTOR> TOptional<RESULT> FindMap( FUNCTOR Func );
Finds and returns value in this iterator that passes
FUNCTOR
predicate, mapped to another type.Note
FUNCTOR
should comply to given function signature:TOptional<Item>(I::Item Value)
whereValue
holds current value yielded by iterator.RESULT
is the returned type and use should always returnTOptional<RESULT>
in the predicate where some value means "found" an none means "not found".Example
// 5.0 const TOptional<float> Result = IterRange(0, 10).FindMap<float>( [](const auto& Value) { if (Value >= 5) { return TOptional<float>(static_cast<float>(Value)); } else { return TOptional<float>(); } });
Arguments
-
Func
FUNCTOR Func
-
-
First
public: TOptional<Item> First();
Returns first item in the iterator.
This is an equivalent of calling
FActorsIter::Next
.Example
// 5 const int Result = IterRange(5, 10).First();
-
Flatten
public: template <typename RESULT> TIterFlatten<typename RESULT, Self> Flatten();
Flattens nested iterators.
Imagine you have an iterator that yields another iterators, such as arrays of arrays.
Note
RESULT
is a type of values yielded by this iterator - it should be the same as nested iterator value type (c++ won't accept that syntax, hence we have to provide aRESULT
type and ensure it's the same),FUNCTOR
should comply to this function signature:RESULT(const I::Item& Value)
.Example
// [0, 1, 2, 3, 4, 0, 1, 2, 3, 4] const TArray<int> P = IterGenerate<TIterRange<int>>([]() { return IterRange(0, 5); }).Take(2).Flatten<int>().CollectArray();
-
Fold
public: template <typename FUNCTOR> Item Fold( Item Start, FUNCTOR Func );
Folds iterator into single value.
Folding basically means going through all iterator items and collapsing them into single value. Example of folding can be sum/accumulation, or min/max, or anything like that - although for ones mentioned there are existing optimized iterator consumers:
FActorsIter::Sum
andFActorsIter::ComparedBy
.Note
RESULT
is the returned type, same asStart
argument used as initial accumulator.FUNCTOR
should comply to given function signature:Item(Item Accumulator, I::Item Value)
whereAccumulator
argument holds result of previous iteration, andValue
holds curren value yielded by iterator.Start
argument holds value passed to first iteratioAccumulator
.Example
// 45 const int Result = IterRange(0, 10).Fold(0, [](const auto& Accum, const auto& Value) { return Accum + Value; });
Arguments
-
ForEach
public: template <typename FUNCTOR> void ForEach( FUNCTOR Func );
Consumes iterator and yields its values for user to process.
This is equivalent of:
auto Iter = IterRange(0, 9); while (const auto Value = Iter.Next()) { const auto Squared = Value * Value; UE_LOG(LogTemp, Warning, TEXT("Squared value: %i"), Squared); }
Note
FUNCTOR
should comply to given function signature:void(I::Item Value)
whereValue
holds current value yielded by iterator.Example
for (const auto Value : IterRange(0, 9)) { const auto Squared = Value * Value; UE_LOG(LogTemp, Warning, TEXT("Squared value: %i"), Squared); }
Arguments
-
Func
FUNCTOR Func
-
-
Inspect
public: template <typename FUNCTOR> TIterInspect<Self, typename FUNCTOR> Inspect( FUNCTOR Func );
Inspects yielded values.
Useful when debugging iterators to for example log what values are yielded at which iterator chain stage.
Note
FUNCTOR
should comply to this function signature:void(const I::Item& Value)
.Example
// [0, 2, 4, 6, 8] const TArray<int> Result = IterRange(0, 10) .Inspect( [](const auto& Value) { // Prints values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. UE_LOG(LogTemp, Warning, TEXT("Inspected item before: %i"), Value); }) .Filter([](const auto& Value) { return Value % 2 == 0; }) .Inspect( [](const auto& Value) { // Prints values: 0, 2, 4, 6, 8. UE_LOG(LogTemp, Warning, TEXT("Inspected item after: %i"), Value); }) .CollectArray();
Arguments
-
Func
FUNCTOR Func
-
-
Last
public: TOptional<Item> Last();
-
Map
public: template <typename RESULT, typename FUNCTOR> TIterMap<typename RESULT, Self, typename FUNCTOR> Map( FUNCTOR Func );
Maps yielded values to another type.
Commonly used for data transformations for use in later stages of iteration.
Note
RESULT
is type that this iterator will yield after data transformations.FUNCTOR
should comply to this function signature:RESULT(const I::Item& Value)
.Example
// [0.0, 4.0, 16.0, 36.0, 64.0] const TArray<float> Result = IterRange(0, 10) .Filter([](const auto& Value) { return Value % 2 == 0; }) .Map<float>([](const auto& Value) { return static_cast<float>(Value * Value); }) .CollectArray();
Arguments
-
Func
FUNCTOR Func
-
-
Next
public: TOptional<Item> Next();
-
Nth
public: TOptional<Item> Nth( uint32 Index );
-
SizeHint
public: IterSizeHint SizeHint() const;
Gets hint about minimum and optional maximum items count this iterator can yield.
See
TQuery::SizeHint
. -
Skip
public: TIterSkip<Self> Skip( uint32 Count );
Skips iteration by number of elements.
It's worth noting that this is one-shot skip, not repeated one, which means if we yield iterator of 10 elements and we skip 2 iterations, then it will skip just 2 values and yield rest 8.
Example
// [3, 4, 5, 6, 7] const TArray<int> Result = IterRange(0, 10).Skip(3).Take(5).CollectArray();
Arguments
-
Count
uint32 Count
-
-
Take
public: TIterTake<Self> Take( uint32 Count );
Limits iterator to at most number of iterations.
If we create an iterator that yields 10 values and we tell it to take 5, then it will stop iterating after next 5 values (or less, depends if there is enough values left in iterator).
Example
// [3, 4, 5, 6, 7] const TArray<int> Result = IterRange(0, 10).Skip(3).Take(5).CollectArray();
Arguments
-
Count
uint32 Count
-
-
Views
public: template <uint32 COUNT> TIterViews<Self, COUNT> Views();
Yields sequences of consecutive views over set of values.
If we create an iterator that yields 5 integer values and we tell it to view 2 at the same time, then it will yield
TArrayView
with values:[0, 1], [1, 2], [2, 3], [3, 4]
.Example
// [(0, 1), (1, 2), (2, 3), (3, 4)] const TArray<TTuple<int, int>> Result = IterRange(0, 5) .Views<2>() .Map<TTuple<int, int>>([](const auto& View) { return MakeTuple(View[0], View[1]); }) .CollectArray();
-
Zip
public: template <typename ITER> TIterZip<Self, ITER> Zip( ITER&& Iter );
-
begin
public: TStlIterator<Self> begin();
Allows this iterator to be used in
for-in
loop.Example
// 0 // 1 // 2 // 3 // 4 for (auto Value : IterRange(0, 5)) { UE_LOG(LogTemp, Info, TEXT("%i"), Value); }
-
end
public: TStlIterator<Self> end();
Allows this iterator to be used in
for-in
loop.Example
// 0 // 1 // 2 // 3 // 4 for (auto Value : IterRange(0, 5)) { UE_LOG(LogTemp, Info, TEXT("%i"), Value); }
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: FArchetypeSignature
struct SYSTEMS_API FArchetypeSignature;
Reflection-enabled
Signature of actor components set.
More in depth about archetype signatures in this architecture book section.
Signatures are used internally by USystemsWorld
to allow
identification of to which buckets (archetypes) given set of components
belongs to - that allows for querying iterators over only these actor
components that contain all requested components and omiting ones that
doesn't, which improves performance of querying systems world (which is the
most frequently used operation in systems game architecture, hence the
importance of making it fast).
Note
Although constructing signatures has no real value for common usecases while developing games this API can be useful for users that needs to perform more advanced operations.
Example
// Instead of this:
const auto A = Systems.Query<UShiaComponent>().Count();
// You can do this:
auto Signature = FArchetypeSignature();
if (const auto Index = Systems.ComponentTypeIndex(UShiaComponent::StaticClass()))
{
Signature.EnableBit(Index.GetValue());
}
const auto B = Systems.CountRaw(Signature);
Methods
-
Clear
public: void Clear();
Clears components set.
-
ContainsAll
public: bool ContainsAll( const FArchetypeSignature& Other ) const;
Checks if this signature fully contain component set of another signature.
Used by
USystemsWorld::Query
to filter archetypes that are valid for iteration.
Arguments
-
Other
const FArchetypeSignature& Other
Other signature to compare with.
-
-
ContainsAny
public: bool ContainsAny( const FArchetypeSignature& Other ) const;
-
CountBits
public: uint32 CountBits() const;
Counts bits of enabled components in set.
-
Difference
public: FArchetypeSignature Difference( const FArchetypeSignature& Other ) const;
-
DisableBit
public: void DisableBit( uint32 Index );
-
EnableBit
public: void EnableBit( uint32 Index );
-
Exclude
public: FArchetypeSignature Exclude( const FArchetypeSignature& Other ) const;
-
FArchetypeSignature
public: FArchetypeSignature();
Constructs signature of empty components set.
-
FArchetypeSignature
public: FArchetypeSignature( const FArchetypeSignature& Other );
-
FArchetypeSignature
public: FArchetypeSignature( uint32 Bit );
-
HasBit
public: bool HasBit( uint32 Index ) const;
-
Include
public: FArchetypeSignature Include( const FArchetypeSignature& Other ) const;
-
IsEmpty
public: bool IsEmpty() const;
Checks if components set is empty.
-
Union
public: FArchetypeSignature Union( const FArchetypeSignature& Other ) const;
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: FInstallSystemOptions
struct SYSTEMS_API FInstallSystemOptions;
Reflection-enabled
Specifiers:
- BlueprintType
Set of options for installation of this system.
It uses builder pattern to simplify setting options.
Note that both FInstallSystemOptions::RunBefore
and
FInstallSystemOptions::RunAfter
methods are accumulative, which
means you can specify with them multiple systems required to run before
and/or after this one.
See USystemsWorld::InstallSystem
, USystemsWorld::InstallLambdaSystem
,
USystemsWorld::InstallSystemRaw
Example
Systems.InstallLambdaSystem(BoidsFaceDirectionSystem,
FInstallSystemOptions("BoidsFaceDirection")
.RunBefore("BoidsFaceDirection")
.RunAfter("BoidsApplyImpulse")
.MultiplayerRunOn(ESystemMultiplayerRunOn::ServerAndClient));
Methods
-
FInstallSystemOptions
public: FInstallSystemOptions( FName Name );
Create new options object.
Example
Systems.InstallLambdaSystem(BoidsFaceDirectionSystem, FInstallSystemOptions("BoidsFaceDirection") .RunBefore("BoidsFaceDirection") .RunAfter("BoidsApplyImpulse") .MultiplayerRunOn(ESystemMultiplayerRunOn::ServerAndClient));
Arguments
-
Name
FName Name
Name of system being installed.
-
-
RunAfter
public: FInstallSystemOptions& RunAfter( FName Name );
Add named dependency system that installed system should run after.
Example
Systems.InstallLambdaSystem(BoidsFaceDirectionSystem, FInstallSystemOptions("BoidsFaceDirection") .RunBefore("BoidsFaceDirection") .RunAfter("BoidsApplyImpulse") .MultiplayerRunOn(ESystemMultiplayerRunOn::ServerAndClient));
Arguments
-
Name
FName Name
Name of other system.
-
-
RunBefore
public: FInstallSystemOptions& RunBefore( FName Name );
Add named dependency system that installed system should run before.
Example
Systems.InstallLambdaSystem(BoidsFaceDirectionSystem, FInstallSystemOptions("BoidsFaceDirection") .RunBefore("BoidsFaceDirection") .RunAfter("BoidsApplyImpulse") .MultiplayerRunOn(ESystemMultiplayerRunOn::ServerAndClient));
Arguments
-
Name
FName Name
Name of other system.
-
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: FMetronome
struct SYSTEMS_API FMetronome;
Reflection-enabled
Specifiers:
- BlueprintType
- Blueprintable
Metronome for ticking optimizations.
Useful for spreading tickable work across time. User should use it to limit number of heavy ticking operations that do not need to perform on every tick, rather can be executed with uniform random time offset for each instance of a group. Good usecase would be AI tasks.
Example
auto Metronome = FMetronome{};
Metronome.Progress(10.0);
if (Metronome.ConsumeTicked())
{
UE_LOG(LogTemp, Warning, TEXT("Metronome ticked!"));
}
Properties
-
Accumulator
public: float Accumulator;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Systems|Metronome
Tells the current time phase.
-
Limit
public: float Limit;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Systems|Metronome
Tells the time limit that marks time of ticking.
-
bTicked
public: bool bTicked;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Systems|Metronome
Tells if metronome has ticked.
Methods
-
ConsumeTicked
public: bool ConsumeTicked();
Chechs if metronome ticked and consumes
FMetronome::bTicked
(sets it tofalse
). -
Progress
public: void Progress( float DeltaTime );
Performs
FMetronome::Accumulator
accumulation byDeltaTime
value.
Arguments
-
DeltaTime
float DeltaTime
-
-
Randomize
public: void Randomize();
Tries to uniformly randomize
FMetronome::Accumulator
value in range from 0 toFMetronome::Limit
.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: FSystemsPipelineAssetResource
struct SYSTEMS_API FSystemsPipelineAssetResource
: public FSystemsPipelineResource;
Reflection-enabled
Specifiers:
- BlueprintType
Pipeline asset resource descriptor.
Properties
-
Proxy
public: TObjectPtr<USystemsPipelineProxyResource> Proxy;
Reflection-enabled
Specifiers:
- EditAnywhere
- Export
- Instanced
- Category = Options
Optionally mark this resource as proxy one for given type.
Useful to unpack wrapper resources and get their inner content that match requested proxy resource type.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: FSystemsPipelineComponent
struct SYSTEMS_API FSystemsPipelineComponent;
Reflection-enabled
Specifiers:
- BlueprintType
Pipeline component descriptor.
Properties
-
bDevelopmentBuildOnly
public: bool bDevelopmentBuildOnly;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Options
Tells if this component should be used in development builds only.
Useful for debug or prototype game features.
-
bUse
public: bool bUse;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Options
Tells if this component should be used.
Useful for quick enable/disable of component without removing it from the pipeline (quick tests).
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: FSystemsPipelineResource
struct SYSTEMS_API FSystemsPipelineResource;
Reflection-enabled
Specifiers:
- BlueprintType
Pipeline resource descriptor.
Properties
-
bDevelopmentBuildOnly
public: bool bDevelopmentBuildOnly;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Options
Tells if this resource should be used in development builds only.
Useful for debug or prototype game features.
-
bUse
public: bool bUse;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Options
Tells if this resource should be used.
Useful for quick enable/disable of resource without removing it from the pipeline (quick tests).
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: FSystemsPipelineSystem
struct SYSTEMS_API FSystemsPipelineSystem;
Reflection-enabled
Specifiers:
- BlueprintType
Pipeline system descriptor.
Properties
-
Template
public: TObjectPtr<USystem> Template;
Reflection-enabled
Specifiers:
- EditAnywhere
- Export
- Instanced
- Category = System
Template of system to instantiate.
-
bDevelopmentBuildOnly
public: bool bDevelopmentBuildOnly;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Options
Tells if this system should be used in development builds only.
Useful for debug or prototype game features.
-
bUse
public: bool bUse;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Options
Tells if this system should be used.
Useful for quick enable/disable of this system without removing it from the pipeline (quick tests).
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: FSystemsPipelineTypeResource
struct SYSTEMS_API FSystemsPipelineTypeResource
: public FSystemsPipelineResource;
Reflection-enabled
Specifiers:
- BlueprintType
Pipeline type resource descriptor.
Properties
-
bUseGlobalStorage
public: bool bUseGlobalStorage;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Options
Tells if this resource should be stored in global storage when pipeline gets uninstalled and restored from global storage when pipeline gets installed.
Useful to transfer resources between persistent maps, like player username typed in main menu and used in game world, or after mission ends, score gets transferred to main menu for display.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: IterSizeHint
struct IterSizeHint;
Hint about minimum and optionally maximum number of items iterator can yield.
See TQuery::SizeHint
.
Example
template <typename I>
void IterCollectIntoArray(I&& Iterator, TArray<typename I::Item>& Result)
{
const auto SizeHint = Iterator.SizeHint();
const auto Capacity = SizeHint.Maximum.IsSet() ? SizeHint.Maximum.GetValue() : SizeHint.Minimum;
Result.Reserve(Result.Num() + Capacity);
while (auto QueryItem = Iterator.Next())
{
Result.Add(QueryItem.GetValue());
}
}
Properties
-
Maximum
public: TOptional<uint32> Maximum;
Maximum number of items iterator can yield.
-
Minimum
public: uint32 Minimum;
Minimum number of items iterator can yield.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: TQuery
template <class... T>
struct TQuery;
Systems world query iterator.
Allows to iterate over actors and their components that comply to requested types signature.
More about iterators in this architecture book page.
Note
User should rather construct queries with
USystemsWorld::Query
instead o constructing queries by hand.
Returned query iterator has always put actor as first item of item tuple and then follow requested components. So
Systems->Query<A, B, C>()
iterator will yield given tupleTTuple<AActor*, A*, B*, C*>
Example
const auto Count = static_cast<int>(Systems.Query<UBoidComponent>().Count());
const auto Difference = Count - EXPECTED_POPULATION_NUMBER;
if (Difference > 0)
{
for (auto& QueryItem : Systems.Query<UBoidComponent>().Take(Difference))
{
auto* Actor = QueryItem.Get<0>();
Actor->Destroy();
}
}
Methods
-
Adapt
public: template <typename ADAPTER> TIterAdapt<Self, ADAPTER> Adapt( ADAPTER Adapter );
Injects custom iterator adapter into the chain of iteration.
Useful only for really custom/advanced solutions that cannot be solved with regular iterators.
Note
ADAPTER
should implement iterator adapter methods. Yielded values share same type wit iterator that wraps this adapter.Example
// [1, 3, 5, 7, 9] const TArray<int> Result = IterRange(0, 10).Adapt(TIterOddAdapter<int>()).CollectArray();
template <typename T> struct TIterOddAdapter { public: template <typename I> TOptional<T> Next(I& Iter) { Iter.Next(); return Iter.Next(); } template <typename I> IterSizeHint SizeHint(const I& Iter) const { return Iter.SizeHint(); } };
Arguments
-
Adapter
ADAPTER Adapter
-
-
All
public: template <typename FUNCTOR> bool All( FUNCTOR Func );
Checks if all values yielded by this iterator passes predicate.
Note
FUNCTOR
should comply to given function signature:bool(I::Item Value)
whereValue
holds current value yielded by iterator.Example
// false const bool Result = IterRange(0, 10).All([](const auto& Value) { return Value > 5; });
Arguments
-
Func
FUNCTOR Func
-
-
Any
public: template <typename FUNCTOR> bool Any( FUNCTOR Func );
Checks if any value yielded by this iterator passes predicate.
Note
FUNCTOR
should comply to given function signature:bool(I::Item Value)
whereValue
holds current value yielded by iterator.Example
// true const bool Result = IterRange(0, 10).Any([](const auto& Value) { return Value > 5; });
Arguments
-
Func
FUNCTOR Func
-
-
Cast
public: template <typename RESULT> TIterCast<typename RESULT, Self> Cast();
Casts yielded values to another type.
Commonly used as a shourtcut for mapping between types using target type constructor.
Note
RESULT
is type that this iterator will yield after casting.Example
// [0.0, 1.0, 2.0, 3.0, 4.0] const TArray<float> Result = IterRange(0, 5).Cast<float>().CollectArray();
-
Chain
public: template <typename ITER> TIterChain<Self, ITER> Chain( ITER&& Iter );
Appends another iterator at the end of this iterator.
Useful for combining results of different iterators that yield same value type.
Note
ITER
should implement iterator methods. Yielded values share same type with this iterato value type.Example
// [0, 1, 2, 3, 4, -5, -4, -3, -2, -1] const TArray<int> Result = IterRange(0, 5).Chain(IterRange(-5, 0)).CollectArray();
Arguments
-
Iter
ITER&& Iter
-
-
CollectArray
public: TArray<Item> CollectArray();
Consumes iterator and returns an array with its values.
Example
const TArray<int> Result = IterRange(0, 10).CollectArray();
-
CollectIntoArray
public: void CollectIntoArray( TArray<Item>& Result );
-
CollectIntoSet
public: void CollectIntoSet( TSet<Item>& Result );
-
CollectSet
public: TSet<Item> CollectSet();
Consumes iterator and returns a set with its values.
Example
const TArray<int> Result = IterRange(0, 10).CollectArray();
-
ComparedBy
public: template <typename FUNCTOR> TOptional<Item> ComparedBy( FUNCTOR Func );
Finds iterator value that compared to other items gets greater "score".
Headline is rather vague, but what it actually does is user can do finding min/max value with this iterator consumer.
Note
FUNCTOR
should comply to given function signature:bool(I::Item A, I::Item B)
whereA
holds current value yielded by iterator andB
is the one that has best "score" so far.Example
// 42 const int Result = IterRange(0, 42).ComparedBy([](const auto A, const auto B) { return A > B; });
Arguments
-
Func
FUNCTOR Func
-
-
Count
public: uint32 Count();
Returns exact number of items that iterator can yield.
Example
// 10 const uint32 Result = IterRange(0, 10).Count();
-
Enumerate
public: TIterEnumerate<Self> Enumerate();
Enumerates values in this iterator.
Useful for reading index of element/iteration.
Note
Yielded values have type:
TTuple<uint32, Item>
, which means this iterator yields tuple o index-value pair.Example
// [0, 1, 2, 3, 4] const TArray<int> Result = IterRepeat(42).Enumerate().Map<int>([](const auto& Pair) { return Pair.Get<0>(); }).Take(5).CollectArray();
-
Filter
public: template <typename FUNCTOR> TIterFilter<Self, typename FUNCTOR> Filter( FUNCTOR Func );
-
FilterMap
public: template <typename RESULT, typename FUNCTOR> TIterFilterMap<typename RESULT, Self, typename FUNCTOR> FilterMap( FUNCTOR Func );
Filters values in the iterator by predicate and maps them to another type.
Note
RESULT
is a type of values yielded by this iterator that maps into.FUNCTOR
should comply to this function signature:TOptional<RESULT>(const I::Item& Value)
Returning some value means pasing it to next iterator, returning none means we omit thi value.Example
// [0.0, 2.0, 4.0, 6.0, 8.0] const TArray<float> I = IterRange(0, 10) .FilterMap<float>( [](const auto& Value) { if (Value % 2 == 0) { return TOptional<float>(static_cast<float>(Value)); } else { return TOptional<float>(); } }) .CollectArray();
Arguments
-
Func
FUNCTOR Func
-
-
Find
public: template <typename FUNCTOR> TOptional<Item> Find( FUNCTOR Func );
Finds and returns value in this iterator that passes
FUNCTOR
predicate.Note
FUNCTOR
should comply to given function signature:bool(I::Item Value)
whereValue
holds current value yielded by iterator.Example
// 5 const TOptional<int> Result = IterRange(0, 10).Find([](const auto& Value) { return Value >= 5; });
Arguments
-
Func
FUNCTOR Func
-
-
FindMap
public: template <typename RESULT, typename FUNCTOR> TOptional<RESULT> FindMap( FUNCTOR Func );
Finds and returns value in this iterator that passes
FUNCTOR
predicate, mapped to another type.Note
FUNCTOR
should comply to given function signature:TOptional<Item>(I::Item Value)
whereValue
holds current value yielded by iterator.RESULT
is the returned type and use should always returnTOptional<RESULT>
in the predicate where some value means "found" an none means "not found".Example
// 5.0 const TOptional<float> Result = IterRange(0, 10).FindMap<float>( [](const auto& Value) { if (Value >= 5) { return TOptional<float>(static_cast<float>(Value)); } else { return TOptional<float>(); } });
Arguments
-
Func
FUNCTOR Func
-
-
First
public: TOptional<Item> First();
Returns first item in the iterator.
This is an equivalent of calling
TQuery::Next
.Example
// 5 const int Result = IterRange(5, 10).First();
-
Flatten
public: template <typename RESULT> TIterFlatten<typename RESULT, Self> Flatten();
Flattens nested iterators.
Imagine you have an iterator that yields another iterators, such as arrays of arrays.
Note
RESULT
is a type of values yielded by this iterator - it should be the same as nested iterator value type (c++ won't accept that syntax, hence we have to provide aRESULT
type and ensure it's the same),FUNCTOR
should comply to this function signature:RESULT(const I::Item& Value)
.Example
// [0, 1, 2, 3, 4, 0, 1, 2, 3, 4] const TArray<int> P = IterGenerate<TIterRange<int>>([]() { return IterRange(0, 5); }).Take(2).Flatten<int>().CollectArray();
-
Fold
public: template <typename FUNCTOR> Item Fold( Item Start, FUNCTOR Func );
Folds iterator into single value.
Folding basically means going through all iterator items and collapsing them into single value. Example of folding can be sum/accumulation, or min/max, or anything like that - although for ones mentioned there are existing optimized iterator consumers:
TQuery::Sum
andTQuery::ComparedBy
.Note
RESULT
is the returned type, same asStart
argument used as initial accumulator.FUNCTOR
should comply to given function signature:Item(Item Accumulator, I::Item Value)
whereAccumulator
argument holds result of previous iteration, andValue
holds curren value yielded by iterator.Start
argument holds value passed to first iteratioAccumulator
.Example
// 45 const int Result = IterRange(0, 10).Fold(0, [](const auto& Accum, const auto& Value) { return Accum + Value; });
Arguments
-
ForEach
public: template <typename FUNCTOR> void ForEach( FUNCTOR Func );
Consumes iterator and yields its values for user to process.
This is equivalent of:
auto Iter = IterRange(0, 9); while (const auto Value = Iter.Next()) { const auto Squared = Value * Value; UE_LOG(LogTemp, Warning, TEXT("Squared value: %i"), Squared); }
Note
FUNCTOR
should comply to given function signature:void(I::Item Value)
whereValue
holds current value yielded by iterator.Example
for (const auto Value : IterRange(0, 9)) { const auto Squared = Value * Value; UE_LOG(LogTemp, Warning, TEXT("Squared value: %i"), Squared); }
Arguments
-
Func
FUNCTOR Func
-
-
Inspect
public: template <typename FUNCTOR> TIterInspect<Self, typename FUNCTOR> Inspect( FUNCTOR Func );
Inspects yielded values.
Useful when debugging iterators to for example log what values are yielded at which iterator chain stage.
Note
FUNCTOR
should comply to this function signature:void(const I::Item& Value)
.Example
// [0, 2, 4, 6, 8] const TArray<int> Result = IterRange(0, 10) .Inspect( [](const auto& Value) { // Prints values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. UE_LOG(LogTemp, Warning, TEXT("Inspected item before: %i"), Value); }) .Filter([](const auto& Value) { return Value % 2 == 0; }) .Inspect( [](const auto& Value) { // Prints values: 0, 2, 4, 6, 8. UE_LOG(LogTemp, Warning, TEXT("Inspected item after: %i"), Value); }) .CollectArray();
Arguments
-
Func
FUNCTOR Func
-
-
Last
public: TOptional<Item> Last();
-
Map
public: template <typename RESULT, typename FUNCTOR> TIterMap<typename RESULT, Self, typename FUNCTOR> Map( FUNCTOR Func );
Maps yielded values to another type.
Commonly used for data transformations for use in later stages of iteration.
Note
RESULT
is type that this iterator will yield after data transformations.FUNCTOR
should comply to this function signature:RESULT(const I::Item& Value)
.Example
// [0.0, 4.0, 16.0, 36.0, 64.0] const TArray<float> Result = IterRange(0, 10) .Filter([](const auto& Value) { return Value % 2 == 0; }) .Map<float>([](const auto& Value) { return static_cast<float>(Value * Value); }) .CollectArray();
Arguments
-
Func
FUNCTOR Func
-
-
Next
public: TOptional<Item> Next();
Yields tuple to next actors component set.
Usually user would rather want to use iterator methods for ergonomic iteration over the query, but in case user has valid reasons to handle iteration different way, this is the single point that performs iteration and yields an item.
Example
auto Query = Systems.Query<UShiaComponent>(); while (auto& QueryItem = Query.Next()) { // Note that we do not check if QueryItem optional value is set. // `while` loop perform these checks for us, hence we use it // instead of standard `for` loop. auto* Actor = QueryItem.GetValue().Get<0>(); auto* Shia = QueryItem.GetValue().Get<1>(); Shia->JustDoIt(Actor); }
-
Nth
public: TOptional<Item> Nth( uint32 Index );
-
SizeHint
public: IterSizeHint SizeHint() const;
Gets hint about minimum and optional maximum items count this iterator can yield.
Used internally by lazy-iterators, but in case user would like to implement their own iterators, or iterator consumers, or just wants to preallocate some memory for later consumed iterator items, this method is really useful for these usecases.
See
IterSizeHint
.Example
template <typename I> void IterCollectIntoArray(I&& Iterator, TArray<typename I::Item>& Result) { const auto SizeHint = Iterator.SizeHint(); const auto Capacity = SizeHint.Maximum.IsSet() ? SizeHint.Maximum.GetValue() : SizeHint.Minimum; Result.Reserve(Result.Num() + Capacity); while (auto QueryItem = Iterator.Next()) { Result.Add(QueryItem.GetValue()); } }
-
Skip
public: TIterSkip<Self> Skip( uint32 Count );
Skips iteration by number of elements.
It's worth noting that this is one-shot skip, not repeated one, which means if we yield iterator of 10 elements and we skip 2 iterations, then it will skip just 2 values and yield rest 8.
Example
// [3, 4, 5, 6, 7] const TArray<int> Result = IterRange(0, 10).Skip(3).Take(5).CollectArray();
Arguments
-
Count
uint32 Count
-
-
Sum
public: Item Sum( Item InitialValue );
Returns sum of values that iterator can yield.
Note
Make sure that type of iterator values actually implement
operator+
! Also since iterator can work on non-numerical types, user has to provide initial value, tha makes it work likeTQuery::Fold
.Example
// 45 const int Return = IterRange(0, 10).Sum(0);
This is equivalent of:
// 45 const int Result = IterRange(0, 10).Fold(0, [](const auto& Accum, const auto& Value) { return Accum + Value; });
Arguments
-
InitialValue
Item InitialValue
-
-
TQuery
public: TQuery( USystemsWorld* Systems );
Constructs query from systems.
An equivalent of calling
USystemsWorld::Query
Arguments
-
Systems
USystemsWorld* Systems
Pointer to systems world of which actor components user wants to iterate on.
-
-
Take
public: TIterTake<Self> Take( uint32 Count );
Limits iterator to at most number of iterations.
If we create an iterator that yields 10 values and we tell it to take 5, then it will stop iterating after next 5 values (or less, depends if there is enough values left in iterator).
Example
// [3, 4, 5, 6, 7] const TArray<int> Result = IterRange(0, 10).Skip(3).Take(5).CollectArray();
Arguments
-
Count
uint32 Count
-
-
Views
public: template <uint32 COUNT> TIterViews<Self, COUNT> Views();
Yields sequences of consecutive views over set of values.
If we create an iterator that yields 5 integer values and we tell it to view 2 at the same time, then it will yield
TArrayView
with values:[0, 1], [1, 2], [2, 3], [3, 4]
.Example
// [(0, 1), (1, 2), (2, 3), (3, 4)] const TArray<TTuple<int, int>> Result = IterRange(0, 5) .Views<2>() .Map<TTuple<int, int>>([](const auto& View) { return MakeTuple(View[0], View[1]); }) .CollectArray();
-
Zip
public: template <typename ITER> TIterZip<Self, ITER> Zip( ITER&& Iter );
-
begin
public: TStlIterator<Self> begin();
Allows this iterator to be used in
for-in
loop.Example
// 0 // 1 // 2 // 3 // 4 for (auto Value : IterRange(0, 5)) { UE_LOG(LogTemp, Info, TEXT("%i"), Value); }
-
end
public: TStlIterator<Self> end();
Allows this iterator to be used in
for-in
loop.Example
// 0 // 1 // 2 // 3 // 4 for (auto Value : IterRange(0, 5)) { UE_LOG(LogTemp, Info, TEXT("%i"), Value); }
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: TReceiverChannel
template <typename T>
struct TReceiverChannel;
SPMC (single-prodicer, multiple-consumer) receiver channel.
See TSenderChannel
.
Example
auto Sender = TSenderChannel<int>();
auto Receiver = Sender.Receiver(1);
Sender.Send(42);
while (const auto Value = Receiver.Receive())
{
UE_LOG(LogTemp, Warning, TEXT("Received value: %i"), Value);
}
Methods
-
Clear
public: void Clear();
Clears internal queue.
User would rather want to read and conume stored messages but sometimes user might have valid reasons to discard all messages without reading them.
-
IsEmpty
public: bool IsEmpty() const;
Checks if there are incoming messages in queue.
-
Receive
public: TOptional<T> Receive();
Consumes and returns one message from internal queue.
Example
auto Sender = TSenderChannel<int>(); auto Receiver = Sender.Receiver(1); Sender.Send(42); while (const auto Value = Receiver.Receive()) { UE_LOG(LogTemp, Warning, TEXT("Received value: %i"), Value); }
-
Unbind
public: void Unbind();
Unbind from sender.
This forcefully unbinds reciever from sender and clears internal queue, though user do not have to call this method when its lifetime ends - receivers unbind themselves as soon as they get destroyed. This method exists only in case user would want to stop receiving further messages on demand, useful in more advanced scenarios.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: TResult
template <typename T, typename E>
struct TResult;
Result type that can wrap either T
value or E
error data.
Useful for return types in functions that can return either valid value or
some error. Given implementation tries to match TOptional
API as much as
possible.
Example
UENUM()
enum class EGuessError : uint8
{
TooHigh,
TooLow,
};
USTRUCT()
struct FShiaTheSecretKeeper
{
GENERATED_BODY()
public:
FShiaTheSecretKeeper() : Secret(), Password()
{
}
FShiaTheSecretKeeper(FString InSecret, int InPassword) : Secret(InSecret), Password(InPassword)
{
}
TResult<FString, EGuessError> TryGuess(int GuessPassword)
{
if (GuessPassword == this->Password)
{
return TResult<FString, EGuessError>(this->Secret);
}
else if (GuessPassword < this->Password)
{
return TResult<FString, EGuessError>(EGuessError::TooLow);
}
else
{
return TResult<FString, EGuessError>(EGuessError::TooHigh);
}
}
private:
FString Secret = {};
int Password = {};
};
int Main()
{
const auto Shia = FShiaTheSecretKeeper("Just do it!", 42);
const auto Result = Shia.TryGuess(42);
if (Result.IsOk())
{
UE_LOG(LogTemp, Info, TEXT("Guessed! Secret: %s"), *Result.GetOk());
}
else
{
switch (Result.GetError())
{
case EGuessError::TooHigh:
UE_LOG(LogTemp, Error, TEXT("Too high!"));
break;
case EGuessError::TooLow:
UE_LOG(LogTemp, Error, TEXT("Too low!"));
break;
}
}
}
Methods
-
AsError
public: TOptional<E> AsError() const;
Returns option with cloned error data.
In case of result wrapping value data, it returns none.
-
AsOk
public: TOptional<T> AsOk() const;
Returns option with cloned value data.
In case of result wrapping error data, it returns none.
-
GetError
public: E& GetError();
-
GetError
public: const E& GetError() const;
-
GetOk
public: T& GetOk();
-
GetOk
public: const T& GetOk() const;
-
IsError
public: bool IsError() const;
Tells if result wraps error data.
-
IsOk
public: bool IsOk() const;
Tells if result wraps value data.
-
SetError
public: void SetError( const E& Error );
-
SetError
public: void SetError( E&& Error );
-
SetOk
public: void SetOk( const T& Value );
-
SetOk
public: void SetOk( T&& Value );
-
TResult
public: TResult( const T& Value );
-
TResult
public: TResult( T&& Value );
-
TResult
public: TResult( const E& Error );
-
TResult
public: TResult( E&& Error );
-
TResult
public: TResult( const TResult& Other );
-
TResult
public: TResult( TResult&& Other );
-
operator bool
public: explicit operator bool() const;
-
operator!=
public: bool operator!=( const TResult& Lhs, const TResult& Rhs );
-
operator<<
public: FArchive& operator<<( FArchive& Ar, TResult& Result );
-
operator=
public: TResult& operator=( const TResult& Other );
-
operator=
public: TResult& operator=( TResult&& Other );
-
operator=
public: TResult& operator=( const T& Value );
-
operator=
public: TResult& operator=( T&& Value );
-
operator=
public: TResult& operator=( const E& Error );
-
operator=
public: TResult& operator=( E&& Error );
-
operator==
public: bool operator==( const TResult& Lhs, const TResult& Rhs );
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: TSenderChannel
template <typename T>
struct TSenderChannel;
SPMC (single-prodicer, multiple-consumer) sender channel.
More in-depth explanation of channels can be found in this architecture book page.
Channels are similar to Unreal events, the difference is while channels sends data that gets stored immediatelly on receiver queue, user have to consume received messages by polling (intentionally asking for next message in queue).
The benefit of polling over pushing (Unreal events) is that user has full controll over when and where incoming messages are processed and executed, and that makes user avoid random/undefined data flow which is common pitfall when using events - with channels we bring determinism into the communication between parts of the game.
Note
User can freely send messages across multiple separate threads, for example if user spawn system on another thread to process some big chunk of data and that system has to send it processing results back to game thread.
User might notice there is method for creating bound receiver, but there is none fo destroying it - the reason is receivers are weakly connected so they will automaticall unbound from sender channel as soon as they get destroyed in their scope, and sender will tr to send any message to that already gone receiver.
Example
auto Sender = TSenderChannel<int>();
auto Receiver = Sender.Receiver(1);
Sender.Send(42);
while (const auto Value = Receiver.Receive())
{
UE_LOG(LogTemp, Warning, TEXT("Received value: %i"), Value);
}
Methods
-
Receiver
public: TReceiverChannel<T> Receiver( uint32 Capacity = 0 );
-
Receivers
public: uint32 Receivers() const;
Returns number of actively bound receivers.
-
Send
public: void Send( const T& Data );
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Struct: TTaggedQuery
template <class... T>
struct TTaggedQuery;
Systems world tagged query iterator.
This is basically an extension to TQuery
that allows to ensure
additional components without the need for reading them - useful for tag
components (ones that just mark actors and do not store any data).
Note
User should rather construct queries with
USystemsWorld::TaggedQuery
instead o constructing queries by hand.
Example
systems_world_tagged_query
Methods
-
Iter
public: TQuery<T...> Iter() const;
Constructs
TQuery
from requested actor components and tag components. -
TTaggedQuery
public: TTaggedQuery( USystemsWorld* InSystems );
Constructs tagged query from systems.
An equivalent of calling
USystemsWorld::TaggedQuery
Arguments
-
InSystems
USystemsWorld* InSystems
Pointer to systems world of which actor components user wants to iterate on.
-
-
With
public: template <class W> TTaggedQuery& With();
-
WithRaw
public: TTaggedQuery& WithRaw( const UClass* Type );
-
Without
public: template <class W> TTaggedQuery& Without();
-
WithoutRaw
public: TTaggedQuery& WithoutRaw( const UClass* Type );
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Classes
ASystemsActor
ASystemsGameMode
ASystemsGameState
ASystemsPawn
FSystemsReflection
UDynamicIterator
UDynamicQuery
ULambdaSystem
UScriptableSystem
USystem
USystemsActorComponent
USystemsGroupComponent
USystemsPipeline
USystemsReflection
USystemsSceneComponent
USystemsStatics
USystemsSubsystem
USystemsWorld
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: ASystemsActor
class SYSTEMS_API ASystemsActor
: public AActor;
Reflection-enabled
Specifiers:
- BlueprintType
- Blueprintable
- Abstract
Base class for systems actors.
Automatically registers this actor components that are not inheriting from
USystemsActorComponent
or USystemsSceneComponent
and have Systems
component tag applied to them. This simply allows Systems
World to recognize and query built-in Unreal Engine components without
registering them manually - one less boilerplate to make on user side.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: ASystemsGameMode
class SYSTEMS_API ASystemsGameMode
: public AGameModeBase;
Reflection-enabled
Base class for game mode that has to install USystemsPipeline
.
Note
Given pipeline will be installed only on server.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: ASystemsGameState
class SYSTEMS_API ASystemsGameState
: public AGameStateBase;
Reflection-enabled
Base class for game state that has to install USystemsPipeline
.
Note
Given pipeline will be installed only on client.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: ASystemsPawn
class SYSTEMS_API ASystemsPawn
: public APawn;
Reflection-enabled
Specifiers:
- BlueprintType
- Blueprintable
- Abstract
Base class for systems actors.
Does exactly the same as ASystemsActor
but for Pawns.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: FSystemsReflection
class SYSTEMS_API FSystemsReflection;
Systems reflection singleton.
Stores application-wide database of named system functions to be used by
ULambdaSystem
.
Methods
-
FindByName
public: TOptional<TFunction<SystemsWorld::LambdaSystemType>> FindByName( FName Name ) const;
-
GetNames
public: void GetNames( TArray<FString>& Result ) const;
-
Register
public: void Register( FName Name, TFunction<SystemsWorld::LambdaSystemType> Callback );
-
Unregister
public: void Unregister( FName Name );
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: UDynamicIterator
class SYSTEMS_API UDynamicIterator
: public UObject;
Reflection-enabled
Specifiers:
- BlueprintType
Methods
-
EstimateSizeLeft
public: virtual int EstimateSizeLeft() const;
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Meta Specifiers:
- DevelopmentOnly
Calculates (usually minimal) number of items this iterator stage can yield.
-
Next
public: virtual UObject* Next();
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Meta Specifiers:
- DevelopmentOnly
Performs iteration and returns yielded value.
Null means no object left to iterate.
-
NextBranched
public: UObject* NextBranched( EDynamicIteratorNextBranch& Branches );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Meta Specifiers:
- ExpandEnumAsExecs = Branches
- DevelopmentOnly
Handy wrapper for
UDynamicIterator::Next
for use in blueprints, where iteration can branch toyielded
andcompleted
execution nodes.
Arguments
-
Branches
EDynamicIteratorNextBranch& Branches
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: UDynamicQuery
class SYSTEMS_API UDynamicQuery
: public UDynamicIterator;
Reflection-enabled
Specifiers:
- BlueprintType
Dynamic query useful for performing system world queries in blueprints.
One of the goals of Systems Architecture is to provide game designers with means to prototype systems without need for programmers. Dynamic query is a way of performing systems world queries in blueprints. Although blueprint API doesn't have same ergonomics as C++ API because of the lack of lazy iterators on blueprints side, technical game designers still might benefit from quickly testing simple systems without involving programmers into the process and being able to quickly test the idea - it makes failing quicker than it would without technical game designers getting their hands dirty once in a while, which is beneficial to the whole production process in long term.
See USystemsWorld::SpawnQuery
.
Example
UCLASS(BlueprintType)
class EXAMPLE_API UShiaQueryBundle
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadOnly)
AActor* Actor = nullptr;
UPROPERTY(BlueprintReadOnly)
UShiaComponent* Shia = nullptr;
};
auto Query = Systems.DynamicQuery<UShiaQueryBundle>();
auto* Bundle = NewObject<UShiaQueryBundle>(this, UShiaQueryBundle::StaticClass());
while (Query->Next(Bundle))
{
Bundle->Shia->JustDoIt(Bundle->Actor);
}
Methods
-
EstimateSizeLeft
public: virtual int EstimateSizeLeft() const override;
Calculates maximum number of items this query can yield.
-
Next
public: virtual UObject* Next() override;
Performs iteration and stores yielded values in returned object.
Note
This method uses reflection to figure out properties that gonna store yielded actor and components in returned object.
-
Setup
public: void Setup( USystemsWorld* Systems, const UClass* Type );
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: ULambdaSystem
class SYSTEMS_API ULambdaSystem
: public USystem;
Reflection-enabled
Specifiers:
- BlueprintType
- Blueprintable
Handy wrapper for state-less systems (functions or lambdas).
See USystem
Example
UFUNCTION()
void BoidsFaceDirectionSystem(USystemsWorld& Systems);
void BoidsFaceDirectionSystem(USystemsWorld& Systems)
{
for (auto& QueryItem : Systems.Query<UVelocityComponent, UBoidComponent>())
{
auto* Actor = QueryItem.Get<0>();
const auto* Velocity = QueryItem.Get<1>();
if (Velocity->Value.IsNearlyZero() == false)
{
Actor->SetActorRotation(Velocity->Value.Rotation());
}
}
}
Systems.InstallLambdaSystem(BoidsFaceDirectionSystem, FInstallSystemOptions("BoidsFaceDirection"));
Methods
-
Bind
public: void Bind( TFunction<FunctorType>&& Func );
Binds given function/lambda that runs some system logic.
Method called by
USystemsWorld::InstallLambdaSystem
Arguments
-
Func
TFunction<FunctorType>&& Func
-
-
Unbind
public: void Unbind();
Unbinds stored function/lambda that runs some system logic.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: UScriptableSystem
class SYSTEMS_API UScriptableSystem
: public USystem;
Reflection-enabled
Specifiers:
- BlueprintType
- Blueprintable
Variant of system that works with blueprints.
User can override OnInit method for system initialization, as well as OnRun for performing system logic.
See USystem
Methods
-
OnInit
public: void OnInit( USystemsWorld* Systems );
Reflection-enabled
Specifiers:
- BlueprintNativeEvent
- BlueprintCallable
- Category = Systems
Meta Specifiers:
- DevelopmentOnly
Override to initialize system (setup its internal state).
See
USystem::Init
Arguments
-
Systems
USystemsWorld* Systems
-
OnRun
public: void OnRun( USystemsWorld* Systems, float DeltaSeconds );
Reflection-enabled
Specifiers:
- BlueprintNativeEvent
- BlueprintCallable
- Category = Systems
Meta Specifiers:
- DevelopmentOnly
Override to run system work logic (perform queries on world components and resources).
See
USystem::Run
Arguments
-
Systems
USystemsWorld* Systems
-
DeltaSeconds
float DeltaSeconds
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: USystem
class SYSTEMS_API USystem
: public UObject;
Reflection-enabled
Specifiers:
- Abstract
- EditInlineNew
Base class for unit of work over some data.
Architecture book page explains more in depth systems.
System's job is to process data. Usually it involves performing queries over game world and/or resources.
This is only one of few ways of system representation, used mostly in case your system has to cache some internal state or has to perform some logic on system initialization phase, which is not possible in case of for example lambda systems.
Example
UCLASS()
class EXAMPLE_API ULogBirdsNumberChangeSystem : public USystem
{
GENERATED_BODY()
public:
virtual void Run(USystemsWorld& Systems) override;
UPROPERTY()
uint32 LastCount = 0;
};
void ULogBirdsNumberChangeSystem::Run(USystemsWorld& Systems)
{
Super::Run(Systems);
if (Systems.ComponentsDidChanged<UBirdComponent>() == false)
{
return;
}
const auto Number = static_cast<int>(Systems.Query<UBirdComponent>().Count());
const Difference = Number - this->LastCount;
this->LastCount = Number;
if (Difference > 0)
{
UE_LOG(LogTemp, Warning, TEXT("Added %i birds"), Difference);
}
else if (Difference < 0)
{
UE_LOG(LogTemp, Warning, TEXT("Removed %i birds"), -Difference);
}
}
Methods
-
AdvancedRun
public: virtual void AdvancedRun( USystemsWorld& Systems, const FName& Mode, const TObjectPtr<UObject>& Payload );
Override to run system work logic in special mode triggered .
This method is called by
USystemsWorld::Process
. See alsoUSystem::Run
.Here user can perform system logic in special mode, like for example systems dispatcher gets request to run systems in network rollback/rollforth mode so systems that support rollback/rollforth can go back in time and re-simulate their part of the game state.
Arguments
-
Systems
USystemsWorld& Systems
Reference to systems world that triggers this system run.
-
Mode
const FName& Mode
Name of the mode system wants to run in (default mode is
None
). -
Payload
const TObjectPtr<UObject>& Payload
Additional payload provided from systems dispatcher that contains extra data for given run mode.
-
-
Cleanup
public: virtual void Cleanup( USystemsWorld& Systems );
Override to cleanup system (its internal state).
This method is called by
USystemsWorld::Cleanup
, right before systems world is scheduled to removal.Here user can perform cleanup of this system internal state, or free acquired resources.
Arguments
-
Systems
USystemsWorld& Systems
Reference to systems world that triggers this system cleanup.
-
-
Init
public: virtual void Init( USystemsWorld& Systems );
Override to initialize system (setup its internal state).
This method is called by
USystemsWorld::SealAndInitialize
, right after systems world is setup and sealed by the user.Here user can perform initialization of this system internal state, or do something useful with systems world (for example initialize some resources). At this stage systems world does not yet have registered any of world components so any query will yield no results.
Example
UCLASS() class BOIDS_API UBoidsMovementSystem : public USystem { GENERATED_BODY() public: virtual void Init(USystemsWorld& Systems) override; private: TReceiverChannel<FMovementStep> MovementStep = {}; }; void UBoidsMovementSystem::Init(USystemsWorld& Systems) { Super::Init(Systems); auto* GameEvents = Systems.Resource<UGameEvents>(); if (IsValid(GameEvents)) { this->MovementStep = GameEvents->MovementStep.Receiver(1); } }
Arguments
-
Systems
USystemsWorld& Systems
Reference to systems world that triggers this system initialization.
-
-
Run
public: virtual void Run( USystemsWorld& Systems );
Override to run system work logic (perform queries on world components and resources).
This method is called by
USystems::AdvancedRun
when systems dispatcher gets request for default mode run.Here user can perform system logic, usually performs single task such as apply velocity accumulated by other systems on actor location, or perform actors AI tasks, or kill actors that has 0 or less health - whatever work you want to do make sure it's not monolithic, rather make it small, like don't do multiple things at once - always divide work to small data processing work units. Split bigger tasks between multiple systems and use components/resources/channels to share data between systems.
Example
UCLASS() class BOIDS_API UBoidsMovementSystem : public USystem { GENERATED_BODY() public: virtual void Run(USystemsWorld& Systems) override; }; void UBoidsMovementSystem::Run(USystemsWorld& Systems) { Super::Run(Systems); const auto* BoidsSettings = Systems.Resource<UBoidsSettings>(); if (IsValid(BoidsSettings) == false) { return; } const auto TimeScale = BoidsSettings->TimeScale; const auto DeltaTime = Systems.GetWorld()->GetDeltaSeconds() * TimeScale; for (auto& QueryItem : Systems.Query<UVelocityComponent, UBoidComponent>()) { auto* Actor = QueryItem.Get<0>(); const auto* Velocity = QueryItem.Get<1>(); const auto Position = Actor->GetActorLocation(); Actor->SetActorLocation(Position + Velocity->Value * DeltaTime); } }
Arguments
-
Systems
USystemsWorld& Systems
Reference to systems world that triggers this system run.
-
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: USystemsActorComponent
class SYSTEMS_API USystemsActorComponent
: public UActorComponent;
Reflection-enabled
Specifiers:
- BlueprintType
- Blueprintable
- Abstract
Meta Specifiers:
- BlueprintSpawnableComponent
Base class for systems actor components.
Architecture book page explains more in
depth actor components relation with systems world but in a brief: they are
data stored in game world actors. You should inherit from this class any new
component class that should get automatically registered to and unregistered
from globally accessible systems worlds in USystemsSubsystem
and then can be queried using USystemsWorld::Query
. You can of
course not inherit components from this class and manually register or
unregister this component at will to any systems world if you want to make
it available from custom or just non-globally accessible systems world, this
class is useful only for automation of registering/unregistering components.
Properties
-
SystemsWorlds
public: TSet<FName> SystemsWorlds;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Systems|Component|Actor
List of subsystem's systems worlds where this component should be registered.
If list is empty, it means this component will be added to every registered systems world.
Methods
-
CanBeRegisteredToSystemsWorld
public: virtual bool CanBeRegisteredToSystemsWorld() const;
Tells if given component can be automatically registered into systems worlds.
Useful to override when component has to meet certain conditions to get considered for automatic registration, conditions such as for example being owned by server game instance, or actor being a pawn.
By default this method always returns
true
. -
IsRegistered
public: bool IsRegistered() const;
Tells if this component is registered to subsystem's systems world.
-
SetRegistered
public: void SetRegistered( bool bMode, bool bForced = false );
Registers this component to subsystem's systems worlds listed in
USystemsActorComponent::SystemsWorlds
.
Arguments
-
bMode
bool bMode
If true, component gets registered, otherwise it gets unregistered.
-
bForced
bool bForced = false
Forces registration change.
Note
Be careful, when
bMode
is true, this will not unregister this component from systems worlds where this component is already registered - this is useful mainly when get called inUActorComponent::BeginPlay
to enforce initial registration when internalbRegister
gets set to true in editor.
-
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: USystemsGroupComponent
class SYSTEMS_API USystemsGroupComponent
: public USystemsActorComponent;
Reflection-enabled
Component that groups actor components of certain type.
The point of this component is to allow Systems World to have a single point of iteration on more than one component of given type, which is not possible to do by default in Systems Architecture, because queries are single-typed.
This is useful mostly for cases where actor might have multiple components of same type which we would like to iterate on.
Note
Since queries require concrete type for each component, it's best to create classes per each components group type and make them inherit this class, so you register that specialized group type into Systems World and use same specialized group type in component queries.
Methods
-
Iter
public: template <typename T> auto Iter();
Gives iterator that yields grouped components that casts to
T
. -
TagIter
public: template <typename T> auto TagIter( FName Tag );
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: USystemsPipeline
class SYSTEMS_API USystemsPipeline
: public UDataAsset;
Reflection-enabled
Specifiers:
- BlueprintType
Data asset used to describe Systems pipeline.
Properties
-
AssetResourcesToInstall
public: TMap<TObjectPtr<UDataAsset>, FSystemsPipelineAssetResource> AssetResourcesToInstall;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Resources|Assets
Asset resources to be registered into systems world.
Usually these are used as settings/config data sources.
-
CleanupSystemsToRun
public: TArray<FSystemsPipelineSystem> CleanupSystemsToRun;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Systems|Cleanup
System templates to instantiate and run on systems world destruction.
Order in the list represents order of execution.
-
ComponentsToRegister
public: TMap<TSubclassOf<UActorComponent>, FSystemsPipelineComponent> ComponentsToRegister;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Components|Types
Typed components to be registered into systems world.
-
PersistentSystemsToInstall
public: TArray<FSystemsPipelineSystem> PersistentSystemsToInstall;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Systems|Persistent
System templates to instantiate and register into systems world to run every tick.
Order in the list represents order of execution.
-
PipelinesToImport
public: TArray<TObjectPtr<USystemsPipeline>> PipelinesToImport;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Pipelines
List of additional pipelines to import setup from when installing into systems world.
-
StartupSystemsToRun
public: TArray<FSystemsPipelineSystem> StartupSystemsToRun;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Systems|Startup
System templates to instantiate and run on systems world creation.
Order in the list represents order of execution.
-
TypeResourcesToInstall
public: TMap<TSubclassOf<UObject>, FSystemsPipelineTypeResource> TypeResourcesToInstall;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Resources|Types
Typed resources to be registered into systems world.
Usually these are used as game managers.
Methods
-
Install
public: void Install( UWorld* World ) const;
Creates new global systems world under
USystemsPipeline::WorldId
name and installs this pipeline content into that systems world.
Arguments
-
World
UWorld* World
World that has running
USystemsSubsystem
.
-
-
InstallInto
public: void InstallInto( USystemsWorld& Systems, TSet<TObjectPtr<USystemsPipeline>>& PipelinesToIgnore ) const;
Installs this pipeline content into given systems world.
Arguments
-
Systems
USystemsWorld& Systems
Target
USystemsWorld
.NOTE: Make sure to install pipeline in its Setup phase, before it gets sealed.
-
PipelinesToIgnore
TSet<TObjectPtr<USystemsPipeline>>& PipelinesToIgnore
List of pipelines to ignore when installing.
This is needed for internal importing mechanism to avoid cycles of cross-referencing pipelines. You most likely might want to leave it empty when called yourself.
-
-
Uninstall
public: void Uninstall( UWorld* World ) const;
Destroys systems world registered under
USystemsPipeline::WorldId
name.
Arguments
-
World
UWorld* World
World that has running
USystemsSubsystem
.
-
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: USystemsReflection
class SYSTEMS_API USystemsReflection
: public UBlueprintFunctionLibrary;
Reflection-enabled
Systems reflection blueprint library.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: USystemsSceneComponent
class SYSTEMS_API USystemsSceneComponent
: public USceneComponent;
Reflection-enabled
Specifiers:
- BlueprintType
- Blueprintable
- Abstract
Meta Specifiers:
- BlueprintSpawnableComponent
Base class for systems scene components.
This is scene component equivalent to USystemsActorComponent
-
scene components are useful where given component should have transformation
relative to actor.
Properties
-
SystemsWorlds
public: TSet<FName> SystemsWorlds;
Reflection-enabled
Specifiers:
- EditAnywhere
- Category = Systems|Component|Scene
List of subsystem's systems worlds where this component should be registered.
If list is empty, it means this component will be added to every registered systems world.
Methods
-
CanBeRegisteredToSystemsWorld
public: virtual bool CanBeRegisteredToSystemsWorld() const;
Tells if given component can be automatically registered into systems worlds.
Useful to override when component has to meet certain conditions to get considered for automatic registration, conditions such as for example being owned by server game instance, or actor being a pawn.
By default this method always returns
true
. -
IsRegistered
public: bool IsRegistered() const;
Tells if this component is registered to subsystem's systems world.
-
SetRegistered
public: void SetRegistered( bool bMode, bool bForced = false );
Registers this component to subsystem's systems worlds listed in
USystemsSceneComponent::SystemsWorlds
.
Arguments
-
bMode
bool bMode
If true, component gets registered, otherwise it gets unregistered.
-
bForced
bool bForced = false
Forces registration change.
Note
Be careful, when
bMode
is true, this will not unregister this component from systems worlds where this component is already registered - this is useful mainly when get called inUActorComponent::BeginPlay
to enforce initial registration when internalbRegister
gets set to true in editor.
-
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: USystemsStatics
class SYSTEMS_API USystemsStatics
: public UBlueprintFunctionLibrary;
Reflection-enabled
Library of useful boilerplate-wrapping functions related to systems components.
Methods
-
AddComponent
public: static void AddComponent( UActorComponent* Component, const TSet<FName>& SystemsWorlds, UObject* WorldContext );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems|Components
Meta Specifiers:
- WorldContext = WorldContext
- UnsafeDuringActorConstruction = true
Tries to add component to specified global systems worlds.
Useful when creating proxy components that inherits from engine components to make them auto added to global systems worlds on
BeginPlay
.
Arguments
-
Component
UActorComponent* Component
Actor component to add to global systems world.
-
SystemsWorlds
const TSet<FName>& SystemsWorlds
List of global systems worlds given component should be registered into.
-
WorldContext
UObject* WorldContext
World context object.
-
AddComponentEverywhere
public: static void AddComponentEverywhere( UActorComponent* Component, UObject* WorldContext );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems|Components
Meta Specifiers:
- WorldContext = WorldContext
- UnsafeDuringActorConstruction = true
Tries to add component to every global systems worlds.
Useful when creating proxy components that inherits from engine components to make them auto added to global systems worlds on
BeginPlay
.
Arguments
-
Component
UActorComponent* Component
Actor component to add to global systems world.
-
WorldContext
UObject* WorldContext
World context object.
-
GetComponentRaw
public: static UActorComponent* GetComponentRaw( FName Id, AActor* Actor, const TSubclassOf<UActorComponent>& Type, UObject* WorldContext );
Reflection-enabled
Specifiers:
- BlueprintPure
- Category = Systems|Components
Meta Specifiers:
- WorldContext = WorldContext
- UnsafeDuringActorConstruction = true
- DisplayName = Get Component
- DeterminesOutputType = Type
Gets actor component from globally registered systems world by its label.
This is a handy shortcut for
USystemsStatics::GetSystemsWorld
and thenUSystemsWorld::ComponentRaw
on it.
Arguments
-
Id
FName Id
Systems world ID.
-
Actor
AActor* Actor
Actor to search in.
-
Type
const TSubclassOf<UActorComponent>& Type
Type of component to search for.
-
WorldContext
UObject* WorldContext
World context object.
-
GetResource
public: template <class T> static T* GetResource( FName Id, UObject* WorldContext );
Gets resource from globally registered systems world by its label.
This is a handy template shortcut for
USystemsStatics::ResourceRaw
.
Arguments
-
Id
FName Id
Systems world ID.
-
WorldContext
UObject* WorldContext
World context object.
-
-
GetResourceRaw
public: static UObject* GetResourceRaw( FName Id, const UClass* Type, UObject* WorldContext );
Reflection-enabled
Specifiers:
- BlueprintPure
- Category = Systems|Resources
Meta Specifiers:
- WorldContext = WorldContext
- UnsafeDuringActorConstruction = true
- DisplayName = Get Resource
- DeterminesOutputType = Type
Gets resource from globally registered systems world by its label.
This is a handy shortcut for
USystemsStatics::GetSystemsWorld
and thenUSystemsWorld::ResourceRaw
on it.
Arguments
-
Id
FName Id
Systems world ID.
-
Type
const UClass* Type
Resource type user wants to get from global systems world.
-
WorldContext
UObject* WorldContext
World context object.
-
GetSystemsWorld
public: static USystemsWorld* GetSystemsWorld( FName Id, UObject* WorldContext );
Reflection-enabled
Specifiers:
- BlueprintPure
- Category = Systems|World
Meta Specifiers:
- WorldContext = WorldContext
- UnsafeDuringActorConstruction = true
Gets globally registered systems world by its label.
Useful in places where we need to perform systems qorld query, but there is no systems world provided into the scope.
Arguments
-
Id
FName Id
Systems world ID.
-
WorldContext
UObject* WorldContext
World context object.
-
RemoveComponent
public: static void RemoveComponent( UActorComponent* Component, UObject* WorldContext );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems|Components
Meta Specifiers:
- WorldContext = WorldContext
- UnsafeDuringActorConstruction = true
Tries to add component to global systems worlds.
Useful when creating proxy components that inherits from engine components to make them auto removed from global systems worlds on
EndPlay
.
Arguments
-
Component
UActorComponent* Component
Actor component to remove from global systems world.
-
WorldContext
UObject* WorldContext
World context object.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: USystemsSubsystem
class SYSTEMS_API USystemsSubsystem
: public UGameInstanceSubsystem;
Reflection-enabled
Global accessible registry of systems worlds.
Architecture book page explains more in depth how to interact with systems subsystem, but in a brief: This is a registry of globally accessible systems worlds game can have at any time.
Example
UCLASS()
class EXAMPLE_API UExampleGameInstance : public UGameInstance
{
GENERATED_BODY()
private:
virtual void Init() override;
};
void UExampleGameInstance::Init()
{
Super::Init();
auto* Subsystem = USystemsSubsystem::Get(GetWorld());
if (IsValid(Subsystem))
{
Subsystem->AcquireSystemsWorld(FName(),
[&](auto& Systems)
{
Systems.RegisterComponent<UShiaComponent>();
Systems.InstallResource<UShiaSettings>();
Systems.InstallLambdaSystem(JustDoItSystem, FInstallSystemOptions("JustDoIt"));
});
}
}
Methods
-
AcquireSystemsWorld
public: void AcquireSystemsWorld( FName Id, TFunction<SystemSetupFunctor> SetupFunctor );
Create, setup and register new systems world.
Example
UCLASS() class EXAMPLE_API AExampleGameMode : public AGameModeBase { GENERATED_BODY() private: virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; }; void AExampleGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) { Super::InitGame(MapName, Options, ErrorMessage); auto* Subsystem = USystemsSubsystem::Get(GetWorld()); if (IsValid(Subsystem)) { Subsystem->AcquireSystemsWorld(FName(), [&](auto& Systems) { Systems.RegisterComponent<UShiaComponent>(); Systems.InstallResource<UShiaSettings>(); Systems.InstallLambdaSystem(JustDoItSystem, FInstallSystemOptions("JustDoIt")); }); } } void AExampleGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason) { Super::EndPlay(EndPlayReason); auto* Subsystem = USystemsSubsystem::Get(GetWorld()); if (IsValid(Subsystem)) { Subsystem->ReleaseSystemsWorld(ThisClass::SYSTEMS_WORLD); } }
Arguments
-
Id
FName Id
Systems world ID.
-
SetupFunctor
TFunction<SystemSetupFunctor> SetupFunctor
Function/lambda that performs setup of created systems world.
Note
SystemSetupFunctor
has given signature:void(USystemsWorld& Systems)
.
-
-
BlueprintAcquireSystemsWorld
public: void BlueprintAcquireSystemsWorld( FName Id, FOnSetupSystemsWorld SetupDelegate );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems|Subsystem
Meta Specifiers:
- DisplayName = Acquire Systems World
Create, setup and register new systems world.
Blueprint-side version of
USystemsSubsystem::AcquireSystemsWorld
.
Arguments
-
Id
FName Id
Systems world ID.
-
SetupDelegate
FOnSetupSystemsWorld SetupDelegate
Delegate that performs setup of created systems world.
Note
This delegate has given signature:
void(USystemsWorld* Systems)
.
-
Get
public: static USystemsSubsystem* Get( UWorld* World );
-
GetSystemsWorld
public: USystemsWorld* GetSystemsWorld( FName Id );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems|Subsystem
Meta Specifiers:
- Displayname = Get Systems World
Gets systems world by its label.
Arguments
-
Id
FName Id
Systems world ID.
-
GetSystemsWorldsIds
public: void GetSystemsWorldsIds( TSet<FName>& Output ) const;
-
ReleaseSystemsWorld
public: void ReleaseSystemsWorld( FName Id );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems|Subsystem
Meta Specifiers:
- Displayname = Release Systems World
Unregister given systems world.
Example
UCLASS() class EXAMPLE_API AExampleGameMode : public AGameModeBase { GENERATED_BODY() private: virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; }; void AExampleGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) { Super::InitGame(MapName, Options, ErrorMessage); auto* Subsystem = USystemsSubsystem::Get(GetWorld()); if (IsValid(Subsystem)) { Subsystem->AcquireSystemsWorld(FName(), [&](auto& Systems) { Systems.RegisterComponent<UShiaComponent>(); Systems.InstallResource<UShiaSettings>(); Systems.InstallLambdaSystem(JustDoItSystem, FInstallSystemOptions("JustDoIt")); }); } } void AExampleGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason) { Super::EndPlay(EndPlayReason); auto* Subsystem = USystemsSubsystem::Get(GetWorld()); if (IsValid(Subsystem)) { Subsystem->ReleaseSystemsWorld(ThisClass::SYSTEMS_WORLD); } }
Arguments
-
Id
FName Id
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Class: USystemsWorld
class SYSTEMS_API USystemsWorld
: public UObject;
Reflection-enabled
Specifiers:
- BlueprintType
Container that holds systems, resources and registry of components that belongs to actors.
Architecture book page explains more in depth what systems world is, but in a brief: systems world is a central point user interacts with using queries to resources and actor components. Think of it as database - actor components are records and resources are unique singleton-like data (usually config/settings or things you would call managers).
Methods
-
Actors
public: FActorsIter Actors();
Acquires lazy-iterator over all registered actors.
This works similar to
USystemsWorld::Query
but it yields only actors without their components, and it yields all actors. This method exists only for a last resort use cases - user should have a valid reason to query all actors and should always try to solve problem withUSystemsWorld::Query
.The only use case i can think of is when user needs to for example count all registered actors, but there are other use cases which can definitely be solved with regular component queries.
Common use case that would be wrong to query actors would be:
for (const auto* Actor : Systems.Actors()) { auto* ShiaActor = Cast<AShiaActor>(Actor); if (IsValid(ShiaActor)) { ShiaActor->JustDoit(); } }
User should instead for example add
UShiaComponent
actor component and query actors usingUSystemsWorld::Query
to iterate only on these actors that are marked as "Shia" actor using actor component tag:for (const auto* Actor : Systems.Query<UShiaComponent>()) { auto* ShiaActor = Cast<AShiaActor>(Actor); if (IsValid(ShiaActor)) { ShiaActor->JustDoit(); } }
Another thing is that user should avoid putting any logic into the actors itself and rather create system that performs work of
AShiaActor::JustDoIt()
method. Although sometimes, mostly in case of interacting with third-party code, user is forced to call logic of actor so in this case just marking actor with component tag would be a sufficient compromise. -
ActorsCount
public: uint32 ActorsCount() const;
Reflection-enabled
Counts all registered actors.
Useful for debugging purposes to show number of registered actors, but any other use case would and most likely should be solved with regular queries.
-
AddComponent
public: bool AddComponent( UActorComponent* Component );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Add actor component to registry.
Called in
USystemsActorComponent::BeginPlay
andUSystemsSceneComponent::BeginPlay
methods so user does not have to, but in case of user dynamically removing actor component to achieve support for behavior toggling, adding components back to registry can be achieved with this method.Note
Actor components are not registered immediately to avoid undefined behavior or even game crashes when performing this while iterating over systems world queries - rather they ar queued and registered after all systems complete their run on current game tick.
Return
True if both actor and component are valid.
Example
void ASomeActor::ToggleTagComponent(USystemsWorld& Systems, UTagComponent* Tag) { this->bTagEnabled = !this->bTagEnabled; if (this->bTagEnabled) { Systems.AddComponent(this, Tag); } else { Systems.RemoveComponent(this, Tag); } }
Arguments
-
Component
UActorComponent* Component
Component to be registered.
-
Cleanup
public: void Cleanup();
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Cleanup existing systems.
Method called in next tick after: [
class: USystemsSubsystem::ReleaseSystemsWorld
]. -
Component
public: template <class T> T* Component( AActor* Actor );
Tries to get pointer to registered actor component.
Handy shortcut for
USystemsWorld::ComponentRaw
Return
Pointer to component or
nullptr
in case component does not exist in registry.Note
T
should inherit fromUActorComponent
Example
Systems.Component<UShiaComponent>(Actor)->JustDoIt();
Arguments
-
Actor
AActor* Actor
Actor owning given component.
-
-
ComponentIndex
public: TOptional<uint32> ComponentIndex( const UActorComponent* Component ) const;
Get component registry index.
Useful when working directly with
FArchetypeSignature
, but user most likely won't have any high-level use case for that.For getting component index by its class use:
USystemsWorld::ComponentTypeIndex
Arguments
-
Component
const UActorComponent* Component
Component which index of we ask for.
-
-
ComponentRaw
public: UActorComponent* ComponentRaw( AActor* Actor, TSubclassOf<UActorComponent> Type );
Reflection-enabled
Specifiers:
- BlueprintPure
- Category = Systems
Meta Specifiers:
- DisplayName = Get Component
- DeterminesOutputType = Type
Tries to get pointer to registered actor component.
Note
Because components are registered after systems run this will always return
nullptr
whe trying to get actor component just after callingUSystemsWorld::AddComponent
Also when trying to get actor component just after calling
USystemsWorld::RemoveComponent
will return given component instead onullptr
because components get unregistered after systems run.Return
Pointer to component or
nullptr
in case component does not exist in registry.Example
Systems.ComponentRaw(Actor, UShiaComponent::StaticClass())->JustDoIt();
Arguments
-
ComponentTypeIndex
public: TOptional<uint32> ComponentTypeIndex( const UClass* Type ) const;
Get component registry index.
Useful when working directly with
FArchetypeSignature
, but user most likely won't have any high-level use case for that.For getting component index by component:
USystemsWorld::ComponentIndex
Arguments
-
Type
const UClass* Type
-
-
Components
public: template <class... T> TTuple<T*...> Components( AActor* Actor );
Gets tuple of actor components.
Handy wrapper for
USystemsWorld::Component
in case of asking for more than one actor component.Note
Works similar way to
USystemsWorld::Query
but do not put actor in its first tuple element, rather gives exactly the pointers to actor components user requests. It's worth noting that in case of component not being registered, it returnsnullptr
in tuple elements corresponding to requested actor component type.
Arguments
-
Actor
AActor* Actor
-
-
ComponentsDidChanged
public: template <class... T> bool ComponentsDidChanged() const;
Tells if components changed during last game tick.
handy wrapper for
USystemsWorld::ComponentsDidChangedRaw
. -
ComponentsDidChangedRaw
public: bool ComponentsDidChangedRaw( const FArchetypeSignature& Signature ) const;
-
ComponentsSignature
public: FArchetypeSignature ComponentsSignature( const TArrayView<UActorComponent*>& View ) const;
Get archetype signature of given set of components.
Useful when working directly with
FArchetypeSignature
, but user most likely won't have any high-level use case for that.
Arguments
-
View
const TArrayView<UActorComponent*>& View
-
-
Count
public: template <class... T> uint32 Count() const;
Counts actors that contain given archetype signature.
This is ergonomic shortcut for
USystemsWorld::CountRaw
that only counts types that should be included.Note
T
classes should inherit fromUActorComponent
!Example
const auto Result = Systems.Count<UShiaComponent>();
-
CountRaw
public: uint32 CountRaw( const FArchetypeSignature& IncludeSignature, const FArchetypeSignature& ExcludeSignature = {} ) const;
Reflection-enabled
Counts actors that contain given archetype include signature and do not contains exclude signature.
This is more performant way of counting actors with given set of components (although non-ergonomic for sure):
// Instead of this: const auto A = Systems.Query<UShiaComponent>().Count(); // You can do this: auto Signature = FArchetypeSignature(); if (const auto Index = Systems.ComponentTypeIndex(UShiaComponent::StaticClass())) { Signature.EnableBit(Index.GetValue()); } const auto B = Systems.CountRaw(Signature);
See
USystemsWorld::Count
for more ergonomic use.Note
For example if requested signature is:
<A, B>
and there are actors:1: A, B, C
and2: A, C
then only actor1: A, B, C
gets counted since only this one contains entire requested signature.
Arguments
-
IncludeSignature
const FArchetypeSignature& IncludeSignature
Archetype signature with minimal set of components that counted actors should contain.
-
ExcludeSignature
const FArchetypeSignature& ExcludeSignature = {}
Archetype signature with minimal set of components that counted actors should not contain.
-
-
DynamicQuery
public: template <class T> UDynamicQuery* DynamicQuery();
Acquires lazy-iterator to dynamically queried actor components.
Handy shortcut for
USystemsWorld::SpawnQuery
.See
UDynamicQuery
-
InstallDefaultResource
public: bool InstallDefaultResource( const UClass* Type );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Register resource object.
See
USystemsWorld::InstallResourceRaw
Return
True if resource was successfully installed (registry is not sealed).
Example
Systems.InstallDefaultResource(UInventory::StaticClass());
Arguments
-
Type
const UClass* Type
Resource class to get constructed and registered.
-
InstallDefaultSystem
public: bool InstallDefaultSystem( const UClass* Type, FInstallSystemOptions Options );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Install system.
See
USystemsWorld::InstallSystemRaw
,USystem
,FInstallSystemOptions
Return
True if system was successfully installed (registry is not sealed).
Example
Systems.InstallDefaultSystem(USomeSystem::StaticClass(), FInstallSystemOptions("Something"));
Arguments
-
InstallLambdaSystem
public: bool InstallLambdaSystem( TFunction<SystemsWorld::LambdaSystemType>&& Functor, FInstallSystemOptions Options = FInstallSystemOptions() );
Install state-less (function or lambda) system.
Stateless systems are the most common ones because usually what system does it only processes the data, so creating function/lambda systems brings more ergonomics into codebase.
Return
True if system was successfully installed (registry is not sealed).
Example
UFUNCTION() void BoidsFaceDirectionSystem(USystemsWorld& Systems); void BoidsFaceDirectionSystem(USystemsWorld& Systems) { for (auto& QueryItem : Systems.Query<UVelocityComponent, UBoidComponent>()) { auto* Actor = QueryItem.Get<0>(); const auto* Velocity = QueryItem.Get<1>(); if (Velocity->Value.IsNearlyZero() == false) { Actor->SetActorRotation(Velocity->Value.Rotation()); } } }
Systems.InstallLambdaSystem(BoidsFaceDirectionSystem, FInstallSystemOptions("BoidsFaceDirection"));
Arguments
-
InstallProxyResource
public: template <class T> bool InstallProxyResource( UObject* Resource, TFunction<SystemsWorld::LambdaFactoryType> Accessor );
Register proxy resource object.
Handy shortcut for
USystemsWorld::InstallProxyResourceRaw
Return
True if resource was successfully installed (registry is not sealed).
Example
UCLASS() class EXAMPLE_API UInventoryWrapper : public UDataAsset { GENERATED_BODY() public: UPROPERTY() UInventory* GeneratedInventory = nullptr; };
auto* Wrapper = NewObject<UInventoryWrapper>(Systems, UInventoryWrapper::StaticClass()); Systems.InstallProxyResource<UInventory>(Wrapper, [](auto* Wrapper) { return Wrapper->GeneratedInventory; });
Arguments
-
InstallProxyResourceRaw
public: bool InstallProxyResourceRaw( const UClass* Type, UObject* Resource, TFunction<SystemsWorld::LambdaFactoryType> Accessor );
Register proxy resource object.
Proxy resources are typically some wrapper objects inner resource we want to access instead of the wrapper one. Basically it does the same what
USystemsWorld::InstallResourceRaw
does, except it allows user to provide unpacking of its inner content.Return
True if resource was successfully installed (registry is not sealed).
Example
UCLASS() class EXAMPLE_API UInventoryWrapper : public UDataAsset { GENERATED_BODY() public: UPROPERTY() UInventory* GeneratedInventory = nullptr; };
Systems.InstallProxyResourceRaw(UInventory::StaticClass(), NewObject<UInventoryWrapper>(Systems, UInventoryWrapper::StaticClass()), [](auto* Wrapper) { return Wrapper->GeneratedInventory; });
Arguments
-
InstallResource
public: template <class T> bool InstallResource();
Register resource object.
Handy shortcut for
USystemsWorld::InstallDefaultResource
Return
True if resource was successfully installed (registry is not sealed).
Example
Systems.InstallResource<UInventory>();
-
InstallResourceRaw
public: bool InstallResourceRaw( UObject* Resource );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Meta Specifiers:
- DisplayName = Install Resource
Register resource object.
It accepts any object that inherits from
UObject
. Also systems world takes ownership over provided resource so its best to not pass any object that has its lifetime managed by other object.Return
True if resource was successfully installed (registry is not sealed).
Example
Systems.InstallResourceRaw(NewObject<UInventory>(Systems, UInventory::StaticClass()));
Arguments
-
Resource
UObject* Resource
Resource object to get registered and managed by this systems world.
-
InstallSystem
public: template <class T> bool InstallSystem( FInstallSystemOptions Options );
Install system.
Handy shortcut for
USystemsWorld::InstallDefaultSystem
See
USystem
,FInstallSystemOptions
Note
Make sure
T
is a class that inherits fromUSystem
!Return
True if system was successfully installed (registry is not sealed).
Example
Systems.InstallSystem<USomeSystem>(FInstallSystemOptions("Something"));
Arguments
-
Options
FInstallSystemOptions Options
System install options.
-
-
InstallSystemRaw
public: bool InstallSystemRaw( USystem* System, FInstallSystemOptions Options );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Meta Specifiers:
- DisplayName = Install System
Install system.
Usually user would want to install systems using either
USystemsWorld::InstallSystem
orUSystemsWorld::InstallLambdaSystem
but in case of valid reasons user can install system by its instance.Note
In case of
FInstallSystemOptions::Label
being empty, it will generate random label from new GUID.See
USystem
,FInstallSystemOptions
Return
True if system was successfully installed (registry is not sealed).
Example
Systems.InstallSystemRaw( NewObject<USomeSystem>(Systems, USomeSystem::StaticClass()), FInstallSystemOptions("Something"));
Arguments
-
IsSealed
public: bool IsSealed() const;
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Tells if systems world is sealed.
Sealed systems world means that it has completed its setup phase and is ready to run or already running.
-
LastChangedComponents
public: const FArchetypeSignature& LastChangedComponents() const;
Reflection-enabled
Returns signature of component types that changed during last game tick.
Useful for more use cases where user needs to cache and perform more advanced change detection between game ticks.
-
LastChangedResources
public: const TSet<uint32>& LastChangedResources() const;
Returns a set of unique type IDs of all resources that changed in last game tick.
Useful for more advanced use cases where user needs to ask for all changes anyway and compare them with some cached set of previously stored changes.
-
MarkComponentChanged
public: void MarkComponentChanged( UActorComponent* Component );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Marks component as changed.
Note
This will mark component type, not component instance, as changed. The need for component instance here is purely to ensure we do not mark components we do not have access to.
Arguments
-
Component
UActorComponent* Component
-
MarkResourceChanged
public: void MarkResourceChanged( UObject* Resource );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Marks given resource as changed.
Useful if user wants to create reactive systems and/or UI that should only trigger when given resource changes. The reason why user has to manually mark resources as changed is for optimizations purposes, to mark deliberate changes in resources instead of marking them automatically, to avoid a lot of boilerplate of that automation, when most of the times systems and UI do not require to ask for changes.
Use
USystemsWorld::ResourceDidChanged
to ask if some resource has changed.
Arguments
-
Resource
UObject* Resource
-
Process
public: void Process();
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Process systems world.
It performs:
- unregistering of removed components and/or actors.
- registering of added components and/or actors.
- run systems logic.
In case of registry not being sealed at the time of calling this method, none of steps above are gonna be performed.
This method is called automatically for global systems world managed by
USystemsSubsystem
, but in case of user managing systems world on their own, user can do:auto* Systems = NewObject<USystemsWorld>(this, USystemsWorld::StaticClass()); // [Systems world setup...] Systems->Process();
-
ProxyResource
public: template <typename T> T* ProxyResource();
-
ProxyResourceRaw
public: UObject* ProxyResourceRaw( const UClass* Type );
Tries to get pointer to proxy resource by its type.
See:
Return
Pointer to proxy resource or
nullptr
in case resource does not exist in registry.Example
auto* Inventory = Cast<UInventory>(Systems.ProxyResourceRaw(UInventory::StaticClass())); Inventory->AddItem(FItem{FItemType::Sword});
Arguments
-
Type
const UClass* Type
Resource type.
-
-
Query
public: template <class... T> TQuery<T...> Query();
Acquires lazy-iterator to query actor components.
More about iterators in this architecture book page.
Queries allow to yield tuples of actor and their components, and only those that comply to given query signature, so there is no iteration over any actor that do not have given component types - actors and components are registered to buckets called archetypes, and archetypes are unique as long as their signature is unique. Signature is constructed from types provided to query, as well as from types registered to systems world that belong to the same actor. Systems architecture focuses on performing queries as fast as possible and not iterating over actors that do not need to be queried was a priority.
See
TQuery
Note
Returned query iterator has always actor put as first item of item tuple and then follow requested components. So
Systems->Query<A, B, C>()
iterator will yield given tupleTTuple<AActor*, A*, B*, C*>
Example
const auto Count = static_cast<int>(Systems.Query<UBoidComponent>().Count()); const auto Difference = Count - EXPECTED_POPULATION_NUMBER; if (Difference > 0) { for (auto& QueryItem : Systems.Query<UBoidComponent>().Take(Difference)) { auto* Actor = QueryItem.Get<0>(); Actor->Destroy(); } }
-
RegisterComponent
public: template <class T> bool RegisterComponent();
Register component type.
Templated shortcut for
USystemsWorld::RegisterComponentRaw
See
UActorComponent
Return
True if component was successfully installed (registry is not sealed and registry haven't reached its capacity).
Note
T
has to be a component that inherits fromUActorComponent
Example
Systems.RegisterComponent<USomeComponent>();
-
RegisterComponentRaw
public: bool RegisterComponentRaw( const UClass* Type );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Meta Specifiers:
- DisplayName = Register Component
Register component type.
Prior to
FArchetypeSignature
being usable for queries and other architecture parts, it has to be able to identify components and for that they has to be registered by their class.See
UActorComponent
Return
True if component was successfully installed (registry is not sealed and registry haven't reached its capacity).
Example
Systems.RegisterComponentRaw(USomeComponent::StaticClass());
Arguments
-
Type
const UClass* Type
Class of component that has to inherit from
UActorComponent
-
RemoveComponent
public: bool RemoveComponent( UActorComponent* Component );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Remove actor component from registry.
Called in
USystemsActorComponent::EndPlay
andUSystemsSceneComponent::EndPlay
methods so user does not have to, but in case of user dynamically adding actor component to achieve support for behavior toggling, removing components from registry can be achieved with this method.Note
Actor components are not unregistered immediately to avoid undefined behavior or eve game crashes when performing this while iterating over systems world queries - rather the are queued and unregistered after all systems complete their run on current game tick.
Return
True if both actor and component are valid.
Example
void ASomeActor::ToggleTagComponent(USystemsWorld& Systems, UTagComponent* Tag) { this->bTagEnabled = !this->bTagEnabled; if (this->bTagEnabled) { Systems.AddComponent(this, Tag); } else { Systems.RemoveComponent(this, Tag); } }
Arguments
-
Component
UActorComponent* Component
Component to be unregistered.
-
Resource
public: template <typename T> T* Resource();
Tries to get pointer to resource by its type.
See:
USystemsWorld::InstallResourceRaw
.USystemsWorld::InstallResource
.USystemsWorld::InstallDefaultResource
.
Return
Pointer to resource or
nullptr
in case resource does not exist in registry.Example
Systems.Resource<UInventory>()->AddItem(FItem{FItemType::Sword});
-
ResourceDidChanged
public: template <class T> bool ResourceDidChanged() const;
Tells if given resource type did changed in last game tick.
Handy wrapper for
USystemsWorld::ResourceDidChangedRaw
. -
ResourceDidChangedRaw
public: bool ResourceDidChangedRaw( const UClass* Type ) const;
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Tells if given resource type did changed in last game tick.
See
USystemsWorld::MarkResourceChanged
.
Arguments
-
Type
const UClass* Type
-
ResourceRaw
public: UObject* ResourceRaw( const UClass* Type );
Reflection-enabled
Specifiers:
- BlueprintPure
- Category = Systems
Meta Specifiers:
- DisplayName = Get Resource
- DeterminesOutputType = Type
Tries to get pointer to resource by its class.
See:
USystemsWorld::InstallResourceRaw
.USystemsWorld::InstallResource
.USystemsWorld::InstallDefaultResource
.
Return
Pointer to resource or
nullptr
in case resource does not exist in registry.Example
auto* Inventory = Cast<UInventory>(Systems.ResourceRaw(UInventory::StaticClass())); Inventory->AddItem(FItem{FItemType::Sword});
Arguments
-
Type
const UClass* Type
Resource class.
-
SealAndInitialize
public: void SealAndInitialize();
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Seal registry and initialize installed systems.
Method called by:
USystemsSubsystem::AcquireSystemsWorld
.When user does not use
USystemsSubsystem
as global systems world registry, or wants to handle systems world on their own, user should call this method after systems world setup (registering components, installation of systems and resources) and then call this method.Note
After calling this method, no further successful system or resource installation i possible, so make sure to install systems world components before sealing systems world!
Example
auto* Systems = NewObject<USystemsWorld>(this, USystemsWorld::StaticClass()); if (IsValid(Systems) == false) { Systems->InstallResource<USomeResource>(); Systems->InstallLambdaSystem(SomeSystem, FInstallSystemOptions("Something")); Systems->SealAndInitialize(); }
-
SpawnQuery
public: UDynamicQuery* SpawnQuery( const UClass* BundleType );
Reflection-enabled
Specifiers:
- BlueprintCallable
- Category = Systems
Meta Specifiers:
- DisplayName = Query
- DevelopmentOnly
Acquires lazy-iterator to dynamically queried actor components.
Because user cannot use templated types in blueprints, dynamic queries are a way to query systems world in blueprints. Also dynamic queries do not implement lazy-iterators so they are definitely not an ergonomic way to iterate over actor components and should be avoided in favor of
USystems::Query
.See
UDynamicQuery
Arguments
-
BundleType
const UClass* BundleType
-
TaggedQuery
public: template <class... T> TTaggedQuery<T...> TaggedQuery();
Acquires lazy-iterator to query actor components with additional tag components.
The difference between
TQuery
is that tagged queries allows to request existence of additional components on actor, ones that are not required for query to access - useful when user do not require any data of given components.See
TTaggedQuery
Example
for (auto& QueryItem : Systems.TaggedQuery<UVelocityComponent>().With<UBoidComponent>().Iter()) { auto* Actor = QueryItem.Get<0>(); const auto* Velocity = QueryItem.Get<1>(); const auto Position = Actor->GetActorLocation(); Actor->SetActorLocation(Position + Velocity->Value * DletaTime); }
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Functions
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Function: IterArray
public:
template <
//// [ignore]
const int N,
//// [/ignore]
typename T>
TIterArray<N, T> IterArray(
std::initializer_list<T> Args
);
Iterator that yields values from fixed size array stored internally.
Example
// [1, 2, 3, 4, 5]
const TArray<int> Result = IterArray<5>({1, 2, 3, 4, 5}).CollectArray();
Arguments
-
Args
std::initializer_list<T> Args
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Function: IterGenerate
public:
template <typename T, typename F>
TIterGenerate<T, F> IterGenerate(
F Functor
);
Iterator that generates values infinitely.
Useful for example for yielding random values, or anything that user would
want to generate procedurally. It is useful to combine it with Take
iterator to limit number of iterations in loop or for collecting values it
yields.
Example
// [?, ?, ?, ?, ?]
const TArray<int> Result = IterGenerate<int>([]() { return FMath::Rand(); }).Take(5).CollectArray();
Arguments
-
Functor
F Functor
Functor that should comply to signature:
T()
.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Function: IterOnce
public:
template <typename T>
TIterOnce<T> IterOnce(
T Value
);
Iterator that yields one value only once.
Useful to use for yielding "leading/trailing/separator" values in chains of multiple iterators.
Example
// [0, 1, 2, 3, 4, -1, 5, 6, 7, 8, 9]
const TArray<int> Result = IterRange(0, 5).Chain(IterOnce(-1)).Chain(IterRange(5, 10)).CollectArray();
Arguments
-
Value
T Value
Value that iterator should yield once.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Function: IterRange
public:
template <typename T>
TIterRange<T> IterRange(
T From,
T To
);
Iterator that yields range of values.
Range:
- inclusive lower bound.
- exclusive upper bound. For range from 0 to 5 it means yielding values: 0, 1, 2, 3, 4.
Note
This iterator is value type agnostic, all it requires from type is for it to implemen
operator++
andoperator-
;
Example
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
const TArray<int> Result = IterRange(0, 10).CollectArray();
Arguments
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Function: IterRepeat
public:
template <typename T>
TIterRepeat<T> IterRepeat(
T Value
);
Iterator that yields one value infinitely.
Useful to combine it with Take
iterator to avoid infinite loops or
collecting iterator values that never ends.
Example
// [1, 1, 1, 1, 1]
const TArray<int> Result = IterRepeat(1).Take(5).CollectArray();
Arguments
-
Value
T Value
Value that iterator should yield infinitely.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Function: IterStd
public:
template <typename T>
TIterStd<T> IterStd(
T& Container
);
Iterator that yields mutable values from standard Unreal Engine collections (such as arrays, sets and maps).
Example
// [0, 1, 2, 3, 4]
TArray<int> Result = IterRange(0, 5).CollectArray();
// [0, 2, 4, 9, 16]
for (auto& Value : IterStd(Result))
{
Value = Value * Value;
}
Arguments
-
Container
T& Container
Reference to Unreal Engine collection.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX
Function: IterStdConst
public:
template <typename T>
TIterStdConst<T> IterStdConst(
const T& Container
);
Iterator that yields immutable values from standard Unreal Engine collections (such as arrays, sets and maps).
Example
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
const TArray<int> ResultA = IterRange(0, 10).CollectArray();
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
const TArray<int> ResultB = IterStd(ResultA).CollectArray();
Arguments
-
Container
const T& Container
Reference to Unreal Engine collection.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX