Scaling to Millions: Implementing the Mass Entity System in Unreal Engine
Stop fighting the Actor bottleneck. Learn how Unreal Engine's Mass Entity System uses Data-Oriented Design to simulate millions of entities using Fragments and Processors.
22 Dec 2025, 05:47 UTC

The Actor Bottleneck
In standard Unreal Engine development, the AActor is the primary unit of logic. However, Actors are heavy. Each one carries significant overhead—transform data, replication logic, and component hierarchies—that consumes memory and CPU cycles. When you need to simulate a crowd of 10,000 NPCs or a swarm of projectiles, the overhead of ticking thousands of individual Actors leads to a frame-rate collapse.
The Mass Entity System (MES) solves this by shifting from an Object-Oriented approach to a Data-Oriented Design (DOD). By implementing an Entity Component System (ECS), Mass separates the data (Components) from the logic (Systems), allowing the CPU to process entities in contiguous memory blocks, which drastically reduces cache misses.
Core Architecture: Entities, Fragments, and Processors
To use Mass effectively, you must stop thinking about "Objects" and start thinking about "Fragments."
- Entities: These are not classes; they are simple IDs. An entity is essentially a collection of fragments.
- Fragments: Small, lightweight C++ structs containing only data (e.g., a
FVectorfor position). Fragments do not contain logic. - Processors: These are the systems that perform the work. A Processor queries for all entities that possess a specific set of fragments and updates them in a batch.
This separation allows Unreal to iterate through thousands of positions in a single linear sweep of memory, rather than jumping between disparate Actor memory addresses.
Practical Implementation: A Basic Movement Processor
To implement a high-performance movement system, you define a fragment for the data and a processor for the logic. This must be done in C++ as the Mass framework relies on template-based queries for performance.
1. Define the Data Fragment
// MovementFragment.h
USTRUCT()
struct FMovementFragment : public FMassFragment
{
GENERATED_BODY()
FVector Velocity = FVector::ZeroVector;
};
2. Create the Processor
The processor uses a UMassEntityQuery to find all entities that have both a Transform and your custom Movement fragment.
// MovementProcessor.cpp
void UMovementProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
EntityQuery.ForEachEntityChunk(EntityManager, Context, [this](FMassExecutionContext& Context)
{
auto TransformList = Context.GetMutableFragmentView<FTransformFragment>();
auto VelocityList = Context.GetMutableFragmentView<FMovementFragment>();
for (int32 i = 0; i < Context.GetNumEntities(); ++i)
{
TransformList[i].GetMutableTransform().AddToTranslation(VelocityList[i].Velocity * Context.GetDeltaTime());
}
});
}
Execution and Permissions
Run these changes by compiling your project in Visual Studio or Rider. Ensure your .Build.cs file includes the "Mass", "MassEntity", and "MassCommon" modules. These processors run on the game thread by default but can be configured for parallel execution via the Mass settings.
Trade-offs and Performance Limitations
Mass is not a replacement for Actors; it is a specialized tool for high-density simulations. The primary trade-off is complexity and visibility. Because entities are just IDs, you cannot simply click on an entity in the viewport to see its variables like you can with an Actor. You must use the Mass Entity Editor utility to inspect state during runtime.
Additionally, synchronizing Mass entities with visual representations (like Static Meshes) requires the Mass Visualization system. If you try to spawn a standard AStaticMeshActor for every Mass entity, you will recreate the exact bottleneck Mass was designed to solve. Instead, use ISM (Instanced Static Meshes) to render the entities.
Verifying System Performance
To verify that your Mass implementation is actually providing a benefit over standard Actors, use the built-in profiling tools:
- Launch the game in Standalone mode.
- Open the console (
~) and runstat unit. - Compare the Frame and Game times when spawning 5,000 Actors versus 5,000 Mass Entities.
- Use the
MassEntityEditorto ensure fragments are updating in real-time without causing hitches.
If you notice a performance dip, check your EntityQuery. Querying for too many fragments or using non-contiguous data structures can force the system to fall back to slower processing paths.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.