RepoPilotOpen in app →

Cysharp/R3

The new future of dotnet/reactive and UniRx.

Healthy

Healthy across all four use cases

Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 2mo ago
  • 9 active contributors
  • MIT licensed
Show 3 more →
  • CI configured
  • Concentrated ownership — top contributor handles 56% of recent commits
  • No test directory detected

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/cysharp/r3)](https://repopilot.app/r/cysharp/r3)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/cysharp/r3 on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: Cysharp/R3

Generated by RepoPilot · 2026-05-10 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/Cysharp/R3 shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across all four use cases

  • Last commit 2mo ago
  • 9 active contributors
  • MIT licensed
  • CI configured
  • ⚠ Concentrated ownership — top contributor handles 56% of recent commits
  • ⚠ No test directory detected

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live Cysharp/R3 repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/Cysharp/R3.

What it runs against: a local clone of Cysharp/R3 — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in Cysharp/R3 | Confirms the artifact applies here, not a fork | | 2 | License is still MIT | Catches relicense before you depend on it | | 3 | Default branch main exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 104 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>Cysharp/R3</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of Cysharp/R3. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/Cysharp/R3.git
#   cd R3
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of Cysharp/R3 and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "Cysharp/R3(\\.git)?\\b" \\
  && ok "origin remote is Cysharp/R3" \\
  || miss "origin remote is not Cysharp/R3 (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
  && ok "license is MIT" \\
  || miss "license drift — was MIT at generation time"

# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
  && ok "default branch main exists" \\
  || miss "default branch main no longer exists"

# 4. Critical files exist
test -f "README.md" \\
  && ok "README.md" \\
  || miss "missing critical file: README.md"
test -f "R3.sln" \\
  && ok "R3.sln" \\
  || miss "missing critical file: R3.sln"
test -f "Directory.Build.props" \\
  && ok "Directory.Build.props" \\
  || miss "missing critical file: Directory.Build.props"
test -f "docs/reference_factory.md" \\
  && ok "docs/reference_factory.md" \\
  || miss "missing critical file: docs/reference_factory.md"
test -f "docs/reference_operator.md" \\
  && ok "docs/reference_operator.md" \\
  || miss "missing critical file: docs/reference_operator.md"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 104 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~74d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/Cysharp/R3"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

R3 is a modern .NET Reactive Extensions library designed as a successor to dotnet/reactive and UniRx, providing high-performance in-memory event processing (LINQ to Events) with native support for 15+ platforms including Unity, Godot, Avalonia, WPF, Blazor, and MAUI. It removes IScheduler entirely, adds frame-based operations critical for game engines, and achieves dramatically better performance and lower allocations through fundamental API redesign aligned with C# 12 and modern reactive patterns like Kotlin Flow and Swift Combine. Monorepo structure: R3.sln is the root project file; source lives in a src/ directory (inferred, not shown in file list); sandbox/ contains seven isolated example projects (AvaloniaApplication1, BlazorApp1, BlazorWebAssemblyApp1, etc.) demonstrating platform-specific integration; docs/ holds reference documentation for factory methods and operators; opensource.snk enables strong naming for NuGet distribution.

👥Who it's for

Game engine developers and cross-platform .NET developers who need efficient event-driven architectures without allocation overhead; contributors to the successor ecosystem of UniRx and dotnet/reactive. Particularly valuable for teams building interactive applications in Unity, Godot, or desktop frameworks who previously used UniRx but need modern C# language features and dramatically better performance.

🌱Maturity & risk

Actively developed and production-ready for many use cases; the project shows serious engineering (benchmark suite in sandbox/Benchmark, comprehensive CI in .github/workflows for both debug and release builds, open-source SNK signing). However, as the 'new future' of Rx, breaking changes are expected as the design stabilizes—this is intentional redesign, not a stable major version library yet.

Primary risk: single-maintainer project (Cysharp organization, creator of UniRx/UniTask with 10+ years Rx experience, but still concentrated). The philosophy deliberately breaks compatibility with dotnet/reactive—migration is not trivial. No visible test directory structure in the file list is a concern; testing strategy is unclear. Dependency on modern C# features (C# 12 syntax) limits backward compatibility to recent .NET versions.

Active areas of work

Active CI/CD pipeline visible: build-debug.yaml and build-release.yaml workflows indicate continuous integration; dependabot.yaml suggests dependency updates are automated. The project is in active development with documentation in docs/reference_factory.md and docs/reference_operator.md being maintained. Multiple sandbox applications suggest ongoing validation across platforms.

🚀Get running

Clone: git clone https://github.com/Cysharp/R3.git && cd R3. Open R3.sln in Visual Studio 2022 or use dotnet build R3.sln. To explore examples, navigate to sandbox/ and open specific .csproj files (e.g., dotnet build sandbox/AvaloniaApplication1/AvaloniaApplication1.csproj). See README.md in the root for platform-specific setup.

Daily commands: Debug build: dotnet build R3.sln -c Debug. Release build: dotnet build R3.sln -c Release. Run benchmarks: dotnet run -p sandbox/Benchmark/Benchmark.csproj -c Release. Run Avalonia example: dotnet run -p sandbox/AvaloniaApplication1/AvaloniaApplication1.csproj. Run Blazor example: dotnet run -p sandbox/BlazorApp1/BlazorApp1.csproj then navigate to https://localhost:5001.

🗺️Map of the codebase

  • README.md — States R3's core philosophy: rejection of IScheduler, emphasis on frame-based operations, and distinction between sync/async—foundational to understanding all design decisions
  • R3.sln — Solution root; entry point for understanding the multi-platform architecture spanning Unity, Godot, Avalonia, WPF, MAUI, Blazor, and more
  • Directory.Build.props — Global build configuration for 600+ files; controls SDK, versioning, and properties applied to all projects in the monorepo
  • docs/reference_factory.md — API documentation for observable factory methods; essential for understanding how to create observables in R3's DSL
  • docs/reference_operator.md — Comprehensive operator reference; the primary user-facing guide to composing reactive pipelines in R3
  • .github/workflows/build-release.yaml — Release CI/CD pipeline; shows how R3 packages and publishes to NuGet across supported platforms
  • opensource.snk — Strong naming key for signed assemblies; required for official NuGet distribution and version identity

🛠️How to make changes

Add a new custom Observable operator

  1. Create a new operator class implementing IObservable<T> or use extension methods on Observable (docs/reference_operator.md)
  2. Define the operator in the core R3 library (location inferred from operator reference docs) (docs/reference_operator.md)
  3. Add examples in the appropriate sandbox app (console, MAUI, Blazor, or Avalonia) (sandbox/ConsoleApp1/Program.cs)
  4. Update reference_operator.md with the new operator signature and usage examples (docs/reference_operator.md)

Add support for a new platform (e.g., FNA, MonoGame fork)

  1. Create platform-specific project in sandbox/ or src/ if core integration needed (sandbox/ConsoleApp1/ConsoleApp1.csproj)
  2. Reference R3 NuGet package with appropriate platform target (Directory.Build.props)
  3. Implement frame-based scheduling if needed (R3's killer feature per README philosophy) (docs/reference_operator.md)
  4. Update README.md with new platform in the support matrix and add sandbox example (README.md)

Publish a new R3 version to NuGet

  1. Update version number in Directory.Build.props (TBD: version property location) (Directory.Build.props)
  2. Push a git tag matching version number (e.g., v1.0.0) (R3.sln)
  3. Trigger build-release.yaml workflow via tag; CI automatically signs and publishes (.github/workflows/build-release.yaml)
  4. Verify NuGet.org shows new version within 10 minutes (Directory.Build.props)

Create a new multi-platform sandbox example (e.g., Stride game engine integration)

  1. Mkdir sandbox/StrideApp1 following naming convention of existing sandbox apps (sandbox/ConsoleApp1)
  2. Create .csproj file with R3 NuGet reference and Stride SDK (sandbox/MauiApp1/MauiApp1.csproj)
  3. Implement main UI/game loop using R3 observables and frame-based operations (sandbox/AvaloniaApplication1/MainWindow.axaml.cs)
  4. Update README.md to list Stride under supported platforms and link sandbox app (README.md)

🔧Why these technologies

  • C# with modern language features (pattern matching, records, nullable reference types) — R3 is built from scratch for C# 8+; leverages contemporary language constructs rather than retrofitting 2009-era Rx code
  • Frame-based scheduling (no IScheduler abstraction) — Game engines and real-time systems require deterministic frame-synchronized updates; R3 bakes this in as a first-class feature
  • Multi-platform targeting (net6.0, net7.0, netstandard2.1, Unity, Godot, WinRT, etc.) — Reflects modern .NET ecosystem: games, MAUI mobile, Blazor web, desktop (WPF/WinUI), and niche platforms all need reactive patterns
  • async/await separation (no Task<T> masquerading as observables) — R3 philosophy: single async operations belong to async/await; observables are for streams. No conflation.
  • MSBuild + Directory.Build.props centralization — 600 files across 10+ platform variants require global version/SDK control to avoid drift and reduce maintenance

⚖️Trade-offs already made

  • Removed IScheduler abstraction entirely

    • Why: IScheduler added indirection, poor performance, and complexity for modern scenarios. Frame-based dispatch is sufficient.
    • Consequence: Not Rx.NET compatible; migration path required from System.Reactive. Cleaner, faster code as tradeoff.
  • OnError does not stop the pipeline

    • Why: Classic Rx philosophy of 'OnError = terminal' was too rigid; real-world streams need error recovery mid-flow
    • Consequence: Different error semantics than Rx.NET; operators must handle errors explicitly. More resilience, more complexity for some scenarios.
  • No synchronous APIs (Pull-based patterns removed)

    • Why: Push-based observables with side effects are clearer; synchronous blocking doesn't compose well with async systems
    • Consequence: Simpler mental model, but users cannot synchronously wait for a value. Must use async/await or frame-based observers.
  • No query comprehension syntax (LINQ in disguise discouraged)

    • Why: Query syntax masks operator complexity and encourages incorrect abstractions (e.g., treating observables as sequences)
    • Consequence: Method-chain style only. More explicit, better IDE hints. Breaks some developer muscle memory from LINQ.
  • Monorepo with 600+ files across multiple sandbox platforms

    • Why: Demonstrates real-world integration for each platform; ensures no accidental breakage during development; single source of truth
    • Consequence: Large clone, but automated CI validates all targets. Single PR can verify MAUI, Avalonia, Blazor, Console, Benchmark simultaneously.

🚫Non-goals (don't propose these)

  • Backward compatibility with System.Reactive (Rx.NET); R3 is a ground-up redesign
  • Synchronous blocking operations (e.g., .Wait(), .Result); async/await is the only concurrency model
  • Hot observable unicasting via separate Subject APIs; design discourages mutable state
  • LINQ query comprehension syntax; method chaining is the primary API style
  • Support for .NET Framework 4.x or .NET 5.0; targets net6.0+ and niche runtimes (Unity, Godot, etc.)
  • Real-time database or messaging integration; R3 is purely a reactive composition library

🪤Traps & gotchas

No IScheduler: Code migrated from dotnet/reactive must be completely refactored—there is no ScheduleOn, ObserveOn, or timing control via the old API. Frame-based scheduling is implicit: Game developers must understand how frame-based execution replaces thread schedulers; Unity/Godot/game-specific APIs differ. Strong naming required: opensource.snk is mandatory for NuGet; building without it will fail signing. C# 12 minimum: Code using older C# syntax will not compile. Query syntax not supported: The README explicitly rejects LINQ query syntax—method chains only.

🏗️Architecture

💡Concepts to learn

  • LINQ to Events — R3's core mission is processing in-memory event streams with LINQ operators; understanding this focus clarifies why backpressure and IScheduler are removed
  • Observer Pattern — R3 reimplements the core Observer pattern with custom disposable subscriptions instead of IObserver<T>/IDisposable; mandatory to understand the API
  • Frame-Based Scheduling — Replaces IScheduler in game engines; critical for synchronizing reactive operations with game loop or UI frames in Unity, Godot, Avalonia
  • Backpressure — R3 explicitly delegates backpressure handling to IAsyncEnumerable and System.Threading.Channels; not implemented in R3 itself
  • IScheduler Anti-Pattern — The README's core critique: IScheduler enables poor performance and complexity; R3 removes it entirely and uses synchronous execution + frame-based queuing instead
  • Subscription Leak Prevention — R3 includes a subscription list mechanism (similar to React DevTools Profiler) to prevent memory leaks; critical for long-running game engines and UI apps
  • Reactive Streams vs. LINQ to Events — R3 explicitly rejects Reactive Streams (backpressure, distributed) in favor of LINQ to Events; understanding this boundary helps with correct library choice for your problem
  • dotnet/reactive — The original .NET Rx that R3 deliberately redesigns and succeeds; understanding dotnet/reactive's limitations (IScheduler, performance) motivates R3's architecture
  • neuecc/UniRx — Creator's previous Rx implementation for Unity; R3 incorporates lessons from 10 years of UniRx use in production game engines
  • Cysharp/UniTask — Sibling async/await runtime library from same author; R3 explicitly delegates single async operations to UniTask rather than reimplementing them
  • reactivex/rxjs — TypeScript Reactive Extensions with modern patterns (no backpressure scheduler, frame-based concepts); design inspiration for R3's philosophy shift
  • Cysharp/MagicOnion — Companion gRPC framework for distributed processing; R3 explicitly states it handles in-memory events while MagicOnion handles distributed messaging

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add comprehensive unit tests for sandbox applications (Avalonia, Blazor, WinForms examples)

The repo contains multiple sandbox applications (AvaloniaApplication1, BlazorApp1, BlazorWebAssemblyApp1) but no visible test projects validating that R3 operators work correctly across different UI frameworks. This is critical for a cross-platform reactive library since platform-specific integration bugs are difficult to catch. Adding integration tests would catch regressions when operators interact with platform schedulers and UI contexts.

  • [ ] Create sandbox/AvaloniaApplication1.Tests/AvaloniaApplication1.Tests.csproj testing UI event streams and subscriptions
  • [ ] Create sandbox/BlazorApp1.Tests/BlazorApp1.Tests.csproj validating reactive state management in Blazor components
  • [ ] Add test cases for frame-based operations (mentioned in README as a core R3 feature) across Avalonia and Blazor
  • [ ] Reference these tests in a new CI workflow sandbox-integration-tests.yaml in .github/workflows/

Document frame-based operations feature with dedicated reference guide

The README mentions 'Frame-based operations, a missing feature in Rx' as a core differentiator of R3, but docs/reference_factory.md and docs/reference_operator.md do not appear to have dedicated sections for this feature. This is a key selling point for game engine users (Unity, Godot, Stride in the platform list) and needs explicit documentation with examples showing how to use frame-based operators and their advantages over time-based scheduling.

  • [ ] Create docs/reference_frame_based_operations.md documenting frame-based scheduling concepts
  • [ ] Add code examples comparing traditional time-based Rx operators with R3's frame-based alternatives
  • [ ] Include integration examples for Unity/Godot showing frame-based operator usage in game loops
  • [ ] Link this document from README.md in relevant platform sections (Unity, Godot, Stride)

Add benchmark suite comparing R3 performance against dotnet/reactive on key scenarios

The repo contains sandbox/Benchmark/Benchmark.csproj but no visible benchmark results or documented performance claims. Given that the README criticizes IScheduler as 'the root of poor performance' and aims to be 'the new future' of reactive extensions, contributors should be able to validate R3's performance improvements with concrete benchmarks. This supports the core value proposition and helps prevent performance regressions.

  • [ ] Extend sandbox/Benchmark/Program.cs with BenchmarkDotNet scenarios comparing R3 vs System.Reactive on: simple filter/map chains, error handling pipelines, and subscription/disposal performance
  • [ ] Add benchmark results documentation to docs/benchmarks.md with methodology and hardware specifications
  • [ ] Integrate benchmarks into build-release.yaml workflow to track performance across releases
  • [ ] Add a benchmark comparison table to README.md showing R3 advantages in key metrics

🌿Good first issues

  • Add unit tests for core Observable factory methods (Observable.Range, Observable.Create, Observable.Return) in docs/reference_factory.md; currently only documented, not tested
  • Implement operator documentation examples in docs/reference_operator.md with runnable code snippets (similar to RxJS or Kotlin Flow docs) for operators like Select, Where, Merge
  • Create a minimal sandbox example project (sandbox/MinimalConsoleApp) showing R3 setup without a UI framework, to help non-game developers onboard

Top contributors

Click to expand

📝Recent commits

Click to expand
  • dc69078 — chore: add groups for dependabot (guitarrapc)
  • 24d0412 — Merge pull request #353 from Cysharp/ci/nuget_release (guitarrapc)
  • 2b37203 — ci: add id-token: write for NuGet Trusted Publish (guitarrapc)
  • 97debd2 — Merge pull request #352 from Cysharp/feature/nuget (guitarrapc)
  • 867e981 — chore: Specify IsPackable=false on Directory.Build.props, explicitly true for target packages. (guitarrapc)
  • 86d2556 — ci: dependabot cooldown 65d2ae (guitarrapc)
  • 5acafe7 — Merge pull request #348 from Cysharp/ci/nuget_readme (guitarrapc)
  • f7bb8ff — ci: need build for pack (guitarrapc)
  • ce65d72 — ci: add dotnet pack and use Release (guitarrapc)
  • 02a92e3 — chore: add README.md to nuget package (guitarrapc)

🔒Security observations

The R3 reactive extensions repository demonstrates a reasonable security posture for an open-source .NET library. No critical vulnerabilities were identified in the visible file structure. The main concerns are: (1) Strong name key file presence without visible key rotation policy, (2) Absence of security documentation and vulnerability disclosure policy, (3) Multiple example applications without security best practices guidance, and (4) Limited visibility into security scanning in the CI/CD pipeline. The project appears to follow standard .NET and GitHub practices (EditorConfig, dependabot configuration, GitHub workflows). Recommend adding comprehensive security documentation and ensuring security scanning is integrated into automated builds.

  • Medium · Strong Name Key File Exposed in Repository — opensource.snk. The file 'opensource.snk' (strong name key file) is present in the repository root. While this appears to be a public open-source key, storing SNK files in version control can pose risks if private keys are accidentally committed or if the key management practices are not properly controlled. Fix: Ensure the SNK file is properly managed. For public open-source projects, document the key rotation policy. Consider storing SNK files outside version control or in a secure key management system for production keys. Document the key's purpose and lifecycle.
  • Low · Multiple Sandbox Applications Without Security Documentation — sandbox/ directory (all subdirectories). The repository contains multiple sandbox/example applications (Blazor, MAUI, Avalonia, WinForms, etc.) that may serve as templates. These lack visible security configuration documentation or security best practices guides. Users could replicate insecure patterns when using these as templates. Fix: Add security.md or SECURITY.md documentation explaining secure practices when using R3 in applications. Include guidance on input validation, dependency updates, and secure defaults for each platform type.
  • Low · Missing Security Policy Documentation — Repository root. No SECURITY.md or security policy file is evident in the repository root. This makes it unclear how security vulnerabilities should be reported or how the project handles security issues. Fix: Create a SECURITY.md file documenting: vulnerability disclosure policy, security contact information, supported versions for security updates, and expected response times for security issues. Reference GitHub's security policy templates.
  • Low · Dependabot Configuration Present Without Visible Update Policy — .github/dependabot.yaml. While dependabot.yaml exists for automated dependency scanning, there is no visible documentation about the project's dependency update policy, security update response times, or version pinning strategy. Fix: Document the project's dependency management strategy: SemVer adherence, security update frequency, patch policy, and any pinned dependency versions. Consider adding automated security scanning in CI/CD pipeline (currently not visible in build workflows).

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · Cysharp/R3 — RepoPilot