By Aliaksandr Zhorau, software engineer at Meta
Entity Component System (ECS) architectures have become a standard approach for scaling modern games. They offer predictable performance, efficient memory usage, and a clean way to extend gameplay logic.
Since 2010 I’ve worked in game development, leading the design and integration of ECS frameworks into live MMO projects, both on proprietary engines and Unreal Engine. What follows are the core lessons drawn from those experiences.
The moment you try to apply ECS to an already shipped, live game, you face a challenge very different from starting with a clean engine. Legacy code, live content, and millions of players impose hard constraints: the engine cannot stop, and no team can afford to rewrite the entire codebase.
In practice, teams embed ECS gradually. This is the way I followed in the projects I worked on: ECS features were introduced piece by piece while the live product continued to operate normally.
What ECS Is and Why It Matters
Before diving into the challenges, it helps to align on what ECS actually is and why so many teams adopt it despite the integration cost. ECS splits gameplay into three concepts:
- Entities – representation of game objects. Access is typically implemented as integer identifiers.
- Components – integral parts of entities. Each entity is a composition of components of various types. Components store data such as velocity, transformation, health, or physical properties.
- Systems – holders of logic performing batch processing of entities with a defined set of components.
Why this matters:
- Modular gameplay updates. Logic is organised in a modular way. New behaviours can be added by implementing a new system querying for the necessary set of entities. At the same time, entities’ behaviour can be changed by adding new components.
- Cache-friendly memory layout. Components’ memory layout can be organised in cache-friendly contiguous arrays. For example, components could be stored in sparse sets (EnTT library) or archetypes (Unreal MassEntity, flecs).
- Natural parallelism. Systems can be organised into graphs with parts of systems running in parallel, enabling efficient scheduling across threads.
In projects with complex game logic ECS paradigm allows more scalable, maintainable and performant architecture.
Why Integration Is Hard
Once the advantages become clear, the next question naturally follows: if ECS is so effective, why isn’t every legacy engine switching immediately? The answer lies in fundamental architectural conflicts and practical integration barriers.
The Chicken-and-Egg Problem
The primary challenge is not technical complexity but integration impossibility. When you implement ECS as “an addition at the side of the engine,” it creates three immediate contradictions with existing systems:
- Entity representation clash. ECS introduces integer-like handles as entities, which directly contradicts legacy OOP-based game object classes. Code cannot seamlessly work with both paradigms without abstraction layers.
- Component memory model mismatch. Even if your engine was component-based before, ECS fundamentally changes component behaviour. Legacy systems assume pointers to components are persistent, and components never move in memory. ECS requires the opposite: components must be relocatable to maintain cache-friendly layouts and packed arrays.
- Logic organisation shift. Systems demand a different approach to writing gameplay logic. If old components followed the OOP paradigm with logic embedded inside them, that logic must be extracted and reorganised into systems that process components in batches.
This creates the chicken-and-egg deadlock: you cannot use ECS effectively until subsystems are migrated, but you cannot migrate subsystems until ECS can interoperate with existing engine foundations.
Additional Technical Friction
Beyond the integration paradox, legacy engines present structural obstacles:
- Pointer-based designs break when components move in memory.
- Subsystems were never built to request data in batches.
- You cannot “bolt on” ECS as a standalone module. It must interoperate with rendering, physics, inventory systems, scripting layers, and UI.
During integration, the approach was to create a unified API that allowed new ECS systems to access legacy components as if they were ECS components. This eliminated the need to duplicate subsystems and enabled gradual migration without maintaining parallel implementations…
Engineering Compromises and Solutions
Because rewriting is impossible, teams rely on pragmatic compromises. These solutions emerge not from theory but from the day-to-day realities of shipping updates to millions of players.
Masking Old vs New Systems
Bridging the gap between legacy architecture and ECS requires deciding how the “old world” ports into the new “ECS world.” The approach I lean toward is multi-step:
- Create direct mapping – establish “old entity → new entity” correspondence.
- Treat old components as ECS components – if your old entity contains non-ECS components, the systems API should allow querying for them in the same manner as native ECS components.
- Handle complex legacy objects – if old entities store data beyond just component lists (similar to Unreal Engine actors, which can be very complex), the API should treat these actors as queryable components.
These approaches enable easier migration by allowing ECS patterns of code writing on the non-ECS part of the engine. However, this is not a magical solution. Systems accessing non-ECS data will have significantly slower performance due to lack of cache-friendly memory layout and less efficient component searches.
Memory Constraints and Pointers
ECS allows the organisation of component memory layout in cache-friendly form. For example, sparse sets (the approach I followed while implementing custom ECS) or archetype-based models. Both approaches are based on tightly packing component memory in contiguous arrays, achieving fast cache-friendly traversal.
One consequence of these implementations is that pointers to the same component constantly change due to removal or addition of new components and rebuilding of sparse sets or archetypes. Legacy code expects stable pointers.
To avoid breaking existing logic, memory addresses for some component types must be frozen, sacrificing a degree of performance to maintain compatibility.
ECS and Scripting Integration
Many games have scripting layers. Lack of ECS integration into scripting severely limits adoption. My experience shows most ECS patterns are exposable to scripting:
- Entities and components can be exposed to the scripting layer similarly to any other native concepts. You are not obliged to follow the same API you have at the native side – entities can be exposed as fully OOP classes, not just integers.
- Systems and their API – the way operations over entities satisfying criteria are written – can also be described in scripting terms.
Obviously, integration into the scripting layer eliminates performance benefits you would have with native ECS. You retain only the other benefit of ECS: a flexible and extendable way of writing logic. This trade-off provided essential extensibility and allowed designers to work without touching C++ code.
Step-by-Step Integration Strategy
Even with compromises in place, integration needs structure. A staged approach prevents system shocks and allows the team to validate each step before moving on.
A workable roadmap:
1. Start with ECS Foundation
Implement ECS, either custom or by picking an existing open-source library. This establishes the core architecture that everything else will build upon.
2. Provide Unified Systems API
Create an ECS systems API that works uniformly with both new entities/components and legacy ones. Implement mapping of the old component model into ECS terms. This enables writing new systems that can access legacy data as if it were ECS data.
3. Gradually Port Existing Components (Optional)
Incrementally migrate existing components to the ECS foundation. This serves two purposes: stress-testing your ECS implementation under real workloads and potentially unlocking performance benefits where cache-friendly layouts matter most.
4. Embed ECS into Scripting System
Integrate ECS into the project’s scripting layer, unlocking its extensibility to a greater extent. This allows gameplay programmers and designers to leverage ECS patterns without touching native code.
5. Monitor Performance Impact
Every migration step should not lead to performance degradation. However, as with any complex system, such projects have many places where performance can break. Continuous monitoring and profiling are essential to catch regressions early.
Defining Success
Success in ECS integration is rarely a single moment. Instead, it appears gradually as new systems start living comfortably alongside the old ones.
ECS integration is “complete enough” when:
- Developers can create new systems in ECS that interact with legacy entities.
- Both old and new subsystems run in parallel without design-breaking edge cases.
- Adding new components or behaviours no longer requires engine-level surgery.
In my experience, a fully ECS-based architecture is rarely achievable in existing projects. In practice, ECS and legacy systems usually coexist within a hybrid model.
Lessons Learned and Final Takeaways
After several long production cycles, the same patterns appeared again and again. These recurring lessons shaped a pragmatic approach to ECS adoption and guided every major decision.
- Never rewrite a live engine outright. Evolve it step by step.
- Abstract differences. Stable APIs allow legacy and ECS objects to coexist.
- Accept performance trade-offs. Practical stability matters more than theoretical purity.
- Preserve scripting flexibility. High-level scripting layers remain essential for fast iteration.
- Aim for coexistence, not replacement. A hybrid architecture is the real end state.
All of this leads to a single conclusion: applying ECS to a live game is not a one‑time migration. It is a gradual evolution shaped by technical constraints, production pressure, and the need to keep a live product stable. Every compromise, API masking, pointer handling, scripting exposure, comes from real engineering choices made to keep the game running while improving its foundations.
A well‑integrated ECS creates a more flexible and maintainable architecture that can support new systems for years without requiring a full rewrite. For live products, this approach is the only sustainable one.

MCV/DEVELOP News, events, research and jobs from the games industry