This article introduces ReactiveEntitySet, a feature coming in v2.1.0 of Reactive SO.
Unity Asset StoreReactive SO | Game ToolkitsScriptableObject-based reactive architecture for Unity. Variables, Event Channels, Runtime Sets, GPU Sync, Reactive Entity Sets, and dedicated debugging windows.It’s based on my research into ECS frameworks and an attempt to bring some of their benefits to traditional Unity development.
In the previous article, I discussed the paradigm shift from “ownership” to “belonging.” This article focuses on the technical decisions behind ReactiveEntitySet.
The Uncomfortable Either-Or
Unity developers often face a binary choice:
- Traditional OOP — GameObject workflows, scattered state, limited optimization options
- Full ECS migration — Steep learning curve, existing code becomes obsolete, completely new workflow
It can feel like being told “abandon everything if you want data-oriented benefits.”
Is there a third way?
Learning from ECS Frameworks
I spent time studying how various ECS frameworks approach entity management:
| Framework | Language | Storage | Reactive Feature |
|---|---|---|---|
| EnTT | C++ | Sparse Set | Signal |
| Entitas | C# | Group | ReactiveSystem |
| Flecs | C/C++ | Archetype | Observer |
| Bevy | Rust | Archetype | Change Detection |
| Svelto.ECS | C# | Group | IReactOn* interfaces |
| Unity DOTS | C# | Archetype | ChangeFilter |
Sparse Set vs Archetype
| Aspect | Sparse Set | Archetype |
|---|---|---|
| Add/Remove | O(1) | O(n) component moves |
| Iteration | Slower (indirection) | Fast (contiguous) |
| Memory | Higher (sparse array) | Lower (packed) |
| Best for | Frequent changes | Large-scale iteration |
What I found interesting: Sparse Set’s weakness — “slow iteration” — assumes you’re iterating every frame.
Reactive Patterns in ECS
I found that “process only on change” isn’t unique to reactive programming — ECS frameworks have similar concepts:
- Entitas ReactiveSystem: Collects only changed entities via
GroupObserver - Flecs Observer: Reacts to
OnAdd/OnRemove/OnSetevents - Bevy Change Detection:
Changed<T>,Added<T>query filters - Svelto.ECS:
IReactOn*interfaces for add/remove/swap events
This was encouraging — the reactive approach isn’t foreign to ECS thinking.
The Key Insight
What if we combine Sparse Set with Reactive patterns?
flowchart LR
subgraph Problem["Sparse Set Weakness"]
W["Slow large-scale iteration"]
end
subgraph Solution["Reactive Characteristic"]
R["Iteration is infrequent"]
end
Problem --> Cancel["Weakness neutralized"]
Solution --> Cancel
Cancel --> Result["Only strengths remain"]
If we rarely iterate (because we only process changes), Sparse Set’s iteration weakness doesn’t seem to matter as much. We keep the O(1) add/remove/access and contiguous data layout.
What I Learned from EnTT
EnTT, used in Minecraft, employs Sparse Set:
- Paginated sparse array for memory efficiency
- O(1) add, remove, and access
- Signal feature for change notifications
What I Learned from Entitas
Entitas introduced the ReactiveSystem concept:
// Only processes entities that changed
public class MovementSystem : ReactiveSystem<GameEntity>
{
protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
=> context.CreateCollector(GameMatcher.Position);
protected override void Execute(List<GameEntity> entities)
{
// Only entities whose position changed arrive here
}
}
This “process only what changed” philosophy resonated with what I was trying to achieve.
ReactiveEntitySet Design
The Core Idea
flowchart LR
RS["RuntimeSet: who exists"] --> RES["ReactiveEntitySet: who exists + what they have"]
ReactiveEntitySet is a natural extension of RuntimeSet. Where RuntimeSet tracks “who exists,” ReactiveEntitySet tracks “who exists and what state they have.”
Data Structure
public class ReactiveEntitySet<TData> : ScriptableObject
where TData : unmanaged
{
// Sparse Set structure
private Dictionary<int, int> idToIndex; // ID → Index
private List<TData> dataList; // Contiguous data
private List<int> indexToId; // Index → ID
// Reactive events
public event Action<int> OnItemAdded;
public event Action<int> OnItemRemoved;
public event Action<int, TData> OnDataChanged;
}
Operation Complexity
| Operation | Complexity |
|---|---|
| Register | O(1) |
| Unregister | O(1) via Swap & Pop |
| Access | O(1) |
Swap & Pop Deletion
To maintain array contiguity, deletion swaps with the last element:
flowchart TB
subgraph Before["Before: Delete B"]
B1["A, B, C, D"]
end
subgraph Swap["Swap: Move D to B position"]
B2["A, D, C, D"]
end
subgraph Pop["Pop: Remove last"]
B3["A, D, C"]
end
Before --> Swap --> Pop
O(1) deletion while keeping the array hole-free.
Comparison with Existing ECS
| Aspect | Unity DOTS | Entitas | ReactiveEntitySet |
|---|---|---|---|
| Learning curve | Steep | Moderate | Gentle |
| Existing code coexistence | Difficult | Moderate | Easy |
| GameObject workflow | Abandoned | Separate | Maintained |
| ScriptableObject-based | No | No | Yes |
| Gradual adoption | Difficult | Possible | Easy |
Positioning
quadrantChart
title Framework Positioning
x-axis GameObject Workflow --> ECS Workflow
y-axis Lower Performance --> Higher Performance
quadrant-1 Full ECS
quadrant-2 High-perf GameObject
quadrant-3 Traditional Unity
quadrant-4 Hybrid approaches
Unity DOTS: [0.85, 0.9]
Svelto.ECS: [0.8, 0.85]
Entitas: [0.7, 0.75]
ReactiveEntitySet: [0.25, 0.6]
Unity Atoms: [0.2, 0.4]
ReactiveEntitySet sits in a space that’s underserved: keeping GameObject workflows while gaining some data-oriented benefits.
What Makes This Combination Unique
ReactiveEntitySet brings together:
- ScriptableObject-based — Editor integration, asset management
- GameObject workflow preserved — Existing code can coexist
- Reactive (change notifications) — Efficient update detection
- Contiguous memory layout — Cache-friendly data access
- Unified API with Event Channels / Variables — Consistent architecture
To my knowledge, there doesn’t appear to be an existing framework that offers this particular combination.
A Note on Performance
The Reactive approach tends to work well when changes are infrequent — which covers many game systems like UI updates, damage events, AI decisions, inventory changes, and state transitions.
I don’t have rigorous benchmarks to share, but the combination of Sparse Set’s O(1) operations and change-based processing seems promising for these use cases. Your results may vary depending on your specific scenario.
Summary
ReactiveEntitySet is an attempt to:
- Selectively adopt ECS insights — Sparse Set, Reactive patterns
- Avoid ECS constraints — Keep GameObject workflows
- Offer a pragmatic middle ground — Not all-or-nothing
For developers who want data-oriented benefits but aren’t ready for full ECS migration, this might be worth exploring.
References
- EnTT GitHub
- Entitas Wiki
- Flecs Manual
- Bevy ECS
- Ownership vs Belonging — The paradigm shift