Mass is a gameplay-focused framework for data-oriented calculations. Is an archetype-based Entity Component System (ECS). It’s designed for managing large amount of entities efficiently.
It enables high-performance simulations of large crowd and complex interactions.
Data oriented design
Data-oriented design is a programming optimization strategy focused on making efficient use of the CPU cache. It emphasizes organizing and transforming data based on when and how it will be used, prioritizing data layout over object hierarchies.
OOP
Traditional object-oriented programming (OOP) tends to result in poor data locality. OOP organizes code around data types and their relationships rather than grouping related fields and arrays in memory for efficient access by specific functions.
ECS
The Entity Component System (ECS) is a software architectural pattern used to represent objects in a game world. Objects are modeled as entities composed of components (data), which are then manipulated by systems.
Mass
Is an archetype-based Entity Component System (ECS).
ECS
Mass
Entity
Entity
Component
Fragment
System
Processor
An entity is a composition of fragments. These fragments get manipulated by processors.
An entity itself is just a unique identifier pointing to fragments. A processor defines a query that filters entities containing specific fragments. For example, a simple movement processor might query entities that have both a transform and velocity fragment, then update their positions by adding velocity to the transform.
Entities
A small unique identifier that references a combination of fragments and tags in memory.
Fragments
Data-only structs that entities can own and processors can query.
An archetype is a unique combination of fragments and tags.
Each archetype holds a bitset containing tag presence information. Each bit represents whether a tag exists in the archetype.
Chunks
Each archetype contains an array of chunks with fragment data. A chunk stores a subset of entities in a struct-of-arrays-like format This maximizes CPU cache efficiency and allows for a great number of whole-entities to fit in the CPU cache.
Chunk sizes are tuned for next-generation cache sizes.
In the image below we can see the difference for Archetype 0 using a chunk versus storing them in a linear way.
The chunked Archetype gets whole-entities in cache, while the Linear Archetype gets all the A Fragments in cache, but cannot fit each fragment of an entity.
Having this chunks we avoid cache misses as we can fit the whole entity in the CPU cache.
Processors
Processors combine user-defined queries with functions that operate on entities.
Processor
UUSTargetSearchProcessor::UUSTargetSearchProcessor() { // Automatically registers the processors with mass bAutoRegisterWithProcessingPhases = true; // Runs on Server and Standalone but not on Client ExecutionFlags = static_cast<int32>(EProcessorExecutionFlags::Server | EProcessorExecutionFlags::Standalone); // Always run this processor before movement and avoidance ExecutionOrder.ExecuteBefore.Add(UE::Mass::ProcessorGroupNames::Movement); ExecutionOrder.ExecuteBefore.Add(UE::Mass::ProcessorGroupNames::Avoidance); // Using the built-in behavior group ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Behavior; // This processor can be multithreaded bRequiresGameThreadExecution = false; }
Queries
Queries filter and iterate entities given a series of rules based on Fragment and Tag presence.
Query configuration
void UUSMoveEntitiesProcessor::ConfigureQueries() { // The processor will read and write the velocity, force and transform from the entity EntityQuery.AddRequirement<FMassVelocityFragment>(EMassFragmentAccess::ReadWrite); EntityQuery.AddRequirement<FMassForceFragment>(EMassFragmentAccess::ReadWrite); EntityQuery.AddRequirement<FTransformFragment>(EMassFragmentAccess::ReadWrite); // The entity cannot be frozen EntityQuery.AddTagRequirement<FUSFrozenTag>(EMassFragmentPresence::None); // The processor will read its movement configuration such as its maximum speed EntityQuery.AddConstSharedRequirement<FMassMovementParameters>(EMassFragmentPresence::All); // The processor will access the UCollissionSubsystem EntityQuery.AddSubsystemRequirement<UCollisionSubsystem>(EMassFragmentAccess::ReadWrite); EntityQuery.RegisterWithProcessor(*this); }
When adding a requirement, you must specify access permissions:
None
ReadOnly
ReadWrite
Processors execute queries in their Execute function. The query receives a lambda where fragments are processed.
Processor execution
void UUSMoveEntitiesProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) { // Starts the query EntityQuery.ForEachEntityChunk(EntityManager, Context, [DeltaTime](FMassExecutionContext& Context) { // Retrieves the fragments const TArrayView<FMassVelocityFragment>& VelocityList = Context.GetMutableFragmentView<FMassVelocityFragment>(); const TArrayView<FMassForceFragment>& ForceList = Context.GetMutableFragmentView<FMassForceFragment>(); const TArrayView<FTransformFragment>& TransformList = Context.GetMutableFragmentView<FTransformFragment>(); const FMassMovementParameters& MoveParams = Context.GetConstSharedFragment<FMassMovementParameters>(); UCollisionSubsystem* CollisionSubsystem = InContext.GetMutableSubsystem<UCollisionSubsystem>(); // Loops over every entity in the current chunk for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) { FMassVelocityFragment& Velocity = VelocityList[EntityIndex]; // ... } });
Mutating entities
Use the Defer function from FMassExecutionContext to modify entities safely.
Observers are specialized processors that react when a fragment or tag is added or removed.
Observer
UCLASS() class UUSDamageObserver : public UMassObserverProcessor{ GENERATED_BODY() UUSDamageObserver() { ExecutionFlags = static_cast<int32>(EProcessorExecutionFlags::AllNetModes); ObservedType = FUSDamageFragment::StaticStruct(); Operation = EMassObservedOperation::Add; } (...)
Traits
Traits are C++ classes that declare a set of fragments and tags, used to create new entities.
To assign traits to an entity, create a DataAsset inheriting from UMassEntityConfigAsset.
Creating a trait
Create a class inheriting from UMassEntityTraitBase and override BuildTemplate.
Mass Trait Base
UCLASS() class UUSAttributeTrait: public UMassEntityTraitBase{ GENERATED_BODY() protected: virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const override { // Initializes the shared fragment FUSAttributeBaseFragment FUSAttributeBaseFragment AttributeBaseFragment; AttributeBaseFragment.MaxHealth = MaxHealth; // Adds the shared fragment to the BuildContext FMassEntityManager& EntityManager = UE::Mass::Utils::GetEntityManagerChecked(World); const FConstSharedStruct& SharedFragment = EntityManager.GetOrCreateConstSharedFragment(AttributeBaseFragment); BuildContext.AddConstSharedFragment(SharedFragment); // Adds the regular fragment FUSAttributeFragment to the BuildContext and initializes it BuildContext.AddFragment_GetRef<FUSAttributeFragment>().CurrentHealth = MaxHealth; } private: UPROPERTY(EditAnywhere) float MaxHealth = 0.0f; };