Skip to main content

Carbonate Release v1.0.0-preview.19

· 7 min read
Calvin Wilkinson
Creator and Maintainer of Velaptor (and other projects)

Hello, and welcome to another Carbonate release!

This one's been a long time coming. Over two years, actually.
The last Carbonate release v1.0.0-preview.18 back in April 2024, and in the time since, a lot has happened across the KinsonDigital ecosystem. Velaptor added macOS support. CASL shipped cross-platform audio with ARM64 builds. And through all of that, Carbonate was quietly doing its job — routing messages, keeping subsystems decoupled, and staying out of the way.

But the library itself needed some love. And that's what this release is about.

Preview Reminder

Carbonate is still in preview! Breaking changes are expected — that's kind of the point. We'd rather fix the API now than lock ourselves into something we regret at v1.0.0.

Check the Carbonate Release Notes for full details. All the work is tracked on GitHub with labeled issues organized into the v1.0.0-preview.19 milestone.

Let's get into it.

Wait, What Even Is Carbonate?

If this is your first time hearing about it — Carbonate is our internal messaging library. It's built on the observable pattern and gives you push/pull message routing without any of your components needing to know about each other.

Think of it as the plumbing. You've got subsystems that need to talk — renderers, audio buffers, input handlers, GPU resource managers — and instead of wiring them together directly (which causes a ton of coupling and can turn into spaghetti real fast), you drop messages through Carbonate. Publishers push. Subscribers react. Nobody knows who's on the other end.

It's the nervous system for both Velaptor and CASL, and we'll talk more about that later. But first — what's new?

.NET 9 and C# 13 (#220)

Carbonate now targets .NET 9 and uses C# 13. This was a ground-up update — every project in the solution got bumped:

  • Carbonate itself
  • The unit tests, perf tests, and integration tests
  • The samples project

The language upgrade unlocks things like params collections, ref in iterators, and the new lock type — nothing that changes the public API, but it modernizes the internals and keeps the library on a supported runtime. .NET 8 goes out of support in November 2026, so this is getting ahead of that cliff.

If you're consuming Carbonate from a .NET 8 project, you'll want to upgrade to .NET 9 (or plan for it soon).

ARM64 Build Configurations (#222)

Not the flashiest feature, but it matters. We added ARM64 build configurations so the solution builds and tests natively on Apple Silicon Macs.

This lines up with what Velaptor and CASL have already done — the whole KinsonDigital stack is moving toward proper ARM64 support, and Carbonate needed to catch up. This is important for Velaptor and CASL to be cross-platform.

CI Matrix Builds (#223)

The build and test workflows now run across Windows, Linux, AND macOS on every commit.

Before this, the CI was Linux-only. If something broke on Windows or macOS, we'd find out when a contributor filed an issue. Now we know immediately. The test matrix and build matrix both cover all three platforms, so regressions get caught before they land.

It's the boring stuff that keeps the project honest. Nobody gets excited about CI configs, but everyone benefits when they work.

Workflow Security (#252)

Our GitHub Actions workflows got a security pass:

  • Actions pinned to commit SHAs instead of floating version tags. This prevents supply-chain surprises where a tag gets moved to a malicious commit.
  • Permissions tightened — workflows now run with least-privilege GITHUB_TOKEN scopes. If a workflow doesn't need contents: write, it doesn't get it.

If you maintain your own GitHub Actions, both of these are worth doing. Seriously.

Switched from FluentAssertions to Shouldly (#257)

We swapped assertion libraries. FluentAssertions served us well, but their license changed to a paid model and that wasn't going to work for us. Shouldly is free, open-source, and does the job.

The migration touched the entire test suite, but the behavior is identical. If you're contributing to Carbonate, you now write your assertions with Shouldly.

Other Bits

A few smaller items rounded out the release:

  • KD-Admin set up (#208) — Automation to help manage the repo.
  • Bluesky link added to the README (#233) — Come say hi! 💙
  • Twitter environment vars updated (#226) — It's called X now. Whatever. 🙄

Dependency Updates

The usual round of dependency bumps came through. NSubstitute v6, the Ubuntu 24.04 runner image, test SDK updates — nothing user-facing, just keeping things current.

What This Means for Velaptor

Carbonate isn't an optional dependency for Velaptor — it's the nervous system of the framework.

Every GPU buffer upload, every shader activation, every render batch flush, every input state change, every content disposal, and every scene transition flows through Carbonate reactables. Velaptor registers 20 reactable types in its DI container and uses 24 notification IDs across three centralized registries. Over 38 source files depend on it.

Here's what Carbonate gives Velaptor:

  • Total decoupling. The GLWindow publishes 8 different notification types but has zero references to its 20+ subscribers. Renderers never call into the Batcher directly. Adding a new subsystem means subscribing to an existing notification — no publisher changes.
  • Type-safe contracts. All 8 message types are readonly record struct — stack-allocated, never boxed. A subscriber can never receive the wrong data type.
  • Dual push/pull. Push for lifecycle events and input state changes. Pull for batch rendering — the RenderMediator fetches current batch items from BatchingManager on demand. One library, one API surface, both patterns.
  • Zero-allocation hot path. The five most frequent signals — batch begin/end, GL init, shutdown, empty batch — use non-directional reactables. No data, no allocation, no boxing. These fire every frame and the overhead is as close to zero as you can get in managed code.
  • Automatic cleanup. Every subscription is an IDisposable. When a component dies, its subscriptions die with it. In a game where objects are created and destroyed constantly, this prevents the memory profile from growing unbounded.

With Carbonate on .NET 9, Velaptor can now pull in this release and get the runtime benefits without touching its own messaging layer. The contracts don't change — the runtime just gets better.

What This Means for CASL

CASL's audio pipeline is built on three Carbonate reactables — two push, one pull — that completely decouple the public Audio API from the internal buffer implementations.

  • API vs. implementation. The Audio class never references FullBuffer or StreamBuffer. It pushes typed messages through IPushReactable<T>, and whichever buffer is active receives them. Buffer strategies can be swapped without touching the public API.
  • Multi-instance audio. Multiple Audio instances play concurrently. Each buffer subscriber filters messages by SourceId, processing only commands for its OpenAL source. Carbonate's broadcast-then-filter pattern makes this trivial.
  • Compile-time safety. AudioCommandData is a readonly record struct. No object casts, no string-keyed dictionaries, no runtime guesswork. The compiler catches contract mismatches at build time.
  • Full testability. IPushReactable<T> and IPullReactable<T> substitute with NSubstitute mocks. CASL's test suite captures subscription callbacks through mocks and invokes them directly — achieving high coverage without spawning real OpenAL contexts.

Same deal as Velaptor: CASL upgrades to this Carbonate release and its messaging layer just works. The .NET 9 upgrade and ARM64 support in Carbonate mean the entire audio stack stays aligned.

Wrapping Up

This release was less about adding flashy new features and more about paying down the technical debt that accumulates when a library sits for two years while the projects that depend on it keep evolving. New runtime, new platforms, better CI, and tighter security.

If you're using Velaptor or CASL, you'll get this Carbonate update the next time those projects cut a release. If you're using Carbonate directly, grab v1.0.0-preview.19 from NuGet and let us know how it goes.

As always — try it, break things, file issues. We're in preview; feedback actually matters.

That's it for this release! 🚀