R3: The Rx Rewrite That Says the Old Rules Were Wrong
Cysharp’s modern reactive library keeps the LINQ feel of Rx, but swaps in a new execution model for .NET 8, game loops, and low-allocation event streams.

The biggest difference is that in normal Rx, when an exception occurs in the pipeline, it flows to OnError and the subscription is unsubscribed, but in R3, it flows to OnErrorResume and the subscription is not unsubscribed. I consider the automatic unsubscription by OnError to be a bad design for event handling.
- R3 is not a cosmetic refresh of Rx.NET. It changes the contract so exceptions can be resumed instead of terminating the stream.
- The library is tuned for interactive software, which is why it treats frames, time, and UI lifecycles as first-class reactive concerns.
- Its architecture removes old Rx machinery like IScheduler and leans on modern .NET primitives such as TimeProvider.
- The sandbox breadth matters because it proves the model works across engines and UI frameworks, not just in a benchmark demo.
R3 opens with a provocation: the classic Rx rule that an exception should end a stream is not sacred. In Cysharp’s view, that behavior is a poor fit for UI, games, and any system that is meant to stay alive after one bad event.
The rule R3 breaks on purpose
That single design choice explains a lot of the repo. If a button handler throws, or a game-loop observer fails for one frame, R3 does not assume the whole reactive world is now invalid. It gives you a path to log, recover, and keep moving.
That is the real thesis of the library. R3 keeps the familiar reactive surface, but it changes what happens when the system gets messy. For long-lived interactive software, that is not a corner case. It is the default.
Why classic Rx feels expensive in 2026
The older Rx model carries historical baggage. `IScheduler` is powerful, but it also pushes complexity into every time-based decision. Add allocation-heavy subscription management, and you get a framework that can feel elegant on paper and fussy in real-time systems.
| Dimension | Rx.NET | UniRx | R3 |
|---|---|---|---|
| Error semantics | `OnError` ends the subscription | `OnError` ends the subscription | `OnErrorResume` keeps the stream alive |
| Timing model | `IScheduler`-centric | Unity-aware, but inherited old Rx assumptions | `TimeProvider` and frame providers |
| Allocation behavior | Heavier subscriber machinery | Optimized for Unity, but legacy-shaped | Designed to reduce allocation pressure |
| Game engine fit | Possible, but not native | Strong for Unity | Built for Unity, Godot, Stride, and more |
| Modern .NET alignment | Older abstraction set | Partial | C# 12+, async/await, `ValueTask`, `TimeProvider` |
R3’s core trick: the stream does not have to die
public abstract class Observer<T>
{
public abstract void OnNext(T value);
public abstract void OnErrorResume(Exception error);
public abstract void OnCompleted();
}
public abstract class Observable<T>
{
public abstract IDisposable Subscribe(Observer<T> observer);
}
The abstract-class design is doing real work here. It gives R3 room to bake in lifecycle behavior and internal optimization that would be awkward to express through interfaces alone. The result is a reactive core that is closer to the runtime than classic Rx tends to be.
That matters because R3 is not trying to win by syntax. It is trying to win by execution model. The `Observer
Built for frames, not just timers
This is where the library becomes obvious to game developers and still interesting to everyone else. R3 treats `FrameProvider` as a native timing source, not a compatibility layer. That makes frame-aligned operators feel like part of the platform instead of a workaround bolted onto it.
The same idea shows up in `TimeProvider`. Instead of leaning on older scheduler abstractions, R3 aligns itself with modern .NET timing primitives. The design is cleaner for tests, easier to reason about, and closer to how current .NET code already wants to talk about time.
A quick mental model
Timer-based reactive code asks, “when should this happen?” Frame-based reactive code asks, “which update cycle should this belong to?” R3 makes that second question feel normal.
The sandbox is the proof
The repo’s `/sandbox` breadth is not decoration. It is the evidence that the same reactive core survives contact with Unity, Godot, Stride, WPF, MAUI, Avalonia, Blazor, and more. Libraries that claim to be universal usually reveal their limits fast. R3 spends a lot of effort proving the opposite.

Stopping the pipeline at OnError is a mistake. IScheduler is the root of poor performance. Frame-based operations, a missing feature in Rx, are especially important in game engines.
That quote is the whole project in miniature. It explains the error philosophy, the performance stance, and the reason frame-based operators are not treated as niche features.
Who built this, and why it matters
R3 comes from Yoshifumi Kawai, better known as neuecc, and from Cysharp’s broader habit of building aggressively practical .NET infrastructure. That background matters because the library’s most opinionated choices are not theoretical. They come from shipping systems that have to perform under real constraints.
| Library | Best at | Main limitation | Why R3 changes the conversation |
|---|---|---|---|
| Rx.NET | The canonical reactive model | Older scheduler and error semantics | R3 keeps the feel but updates the runtime contract |
| UniRx | Unity-friendly reactive code | Legacy architecture | R3 keeps the engine fit while modernizing the core |
| ReactiveProperty | MVVM state binding | Narrower scope | R3 absorbs much of the reactive state story natively |
| DynamicData | Reactive collections | Specialized domain focus | R3 pairs broader reactive primitives with modern platform support |





