The .NET ecosystem is dynamically evolving in a number of beneficial ways, and we want to bring those improvements to you as soon as we can. Our internal .NET Tech Group works on continuous improvement of our .NET integration, including newer C# features and .NET Standard 2.1. But we’ve recently kicked things into a higher gear to improve your developer experience across the board, based on your feedback.
This blog post introduces the issues we’re working on. We’ve also discussed this topic at the Unity Dev Summit at GDC 2022. You can watch the full session here.
The evolution of .NET and Unity
The story starts 17 years ago, when our CTO started leveraging the Mono .NET runtime with C#. Unity favored C# due to its simplicity, combined with a JIT (just-in-time) compiler that translates your C# into relatively efficient native code. The remaining and much larger parts of the Unity engine have been developed using C++ in order to provide well-balanced and controlled performance.
For many years, Unity had been running with a specific fork of the Mono .NET runtime and C# language (2.0). During that time, we’ve added support for additional platforms. We’ve also developed our own compiler and runtime, IL2CPP, to enable you to target iOS and some console platforms.
In the meantime, the overall Microsoft .NET ecosystem has evolved, with new licensing and support for non-Windows platforms. This evolution has allowed us to upgrade the Unity .NET Mono Runtime in 2018 and embrace more modern C# language versions (7.0+). The same year, we also released the first version of the Burst compiler, pioneering fast native code generated for a subset of the C# language. This breakthrough allowed Unity to envision a world where we could extend the usage of C# in the other critical segments of the engine without having to develop these parts in C++, leading to the development of the DOTS runtime.
Unity 2020 LTS and Unity 2021 LTS brought newer C# language versions and .NET API like Span<T>. In parallel, we have seen tremendous performance improvements be delivered in the .NET ecosystem, as well as a more friendly development environment with the introduction of SDK style csproj and the flourishing NuGet ecosystem.
What we need to do
As a result of this long evolution, the Unity platform includes a very large C++ codebase that interacts directly with .NET objects using specific assumptions inherited from the Mono .NET Runtime. These are no longer valid or efficient for the .NET (Core) Runtime.
Furthermore, there’s a complicated custom compilation pipeline bound to the Unity Editor that doesn't rely on MSBuild and thus cannot easily benefit from all standard features.
We’ve also been talking to many of you over the past few years, both in interviews and on the Unity forum, to see what we could improve to better enable your success. What we’ve heard is that you want to use the latest C# language, the .NET runtime technology, and third-party C# code from NuGet. When it comes to using the Unity platform, you told us you wanted to get the maximum out of the target hardware with high-quality C# testing, debugging and profiling tools, and good integration between standard .NET API and the Unity API. As a C# Unity programmer, you want Unity tools that seamlessly work with the rest of your toolbox and enable rapid iteration so that you can achieve best-in-class runtime performance.
Getting there is going to take us several years. We’ll keep you in the loop with frequent blog and forum updates on the technical challenges that we encounter along the way.
How we’re going to do it
Our first step on this initiative was to huddle with all the internal people passionate about C# and .NET in Unity to form a C#/.NET Tech Group to drive this effort.
We want to build on top of the .NET ecosystem instead of developing custom solutions. To enable you to take advantage of the performance and productivity improvements that come with the latest .NET SDK/Runtime and MSBuild, we want to migrate from the Mono .NET Runtime to CoreCLR, the modern .NET (Core) Runtime.
This initiative is also bringing you innovation beyond the existing .NET universe, with the goals of delivering faster .NET iteration cycles on your C# scripts. We’ll be working on converging the JIT and AOT (ahead-of-time) solutions – IL2CPP and Burst – to offer the best balance between compile time efficiency and CodeGen quality.
Externally, we’re working with partners in the industry like Microsoft and JetBrains to ensure that Unity creators are using the latest .NET technology. We’re also ramping up our participation in open-source communities. We’re going to break down this endeavor into several steps. Let’s see what’s coming next.
What we’re working on in 2022
This year, the teams are planning to work on the following tracks.
C# development workflow
Iteration time remains our top priority since we know that you want to get more out of your time. Here are a few examples of what we’re doing to improve this.
For the migration to MSBuild, the first step is to decouple our compilation pipeline from the Unity Editor and move it to a separate process. This is a complicated operation because there are years of legacy code with thousands of lines of C++ and C# code that we need to untangle in order to achieve this – while also staying backward compatible. You won’t see changes from your point of view, but it’s going to pave our path to MSBuild and simplify maintenance.
We’re also going to improve the C# IDE debugging experience with Burst by introducing a mode that will automatically switch the debugger to managed debugging when a breakpoint is set on a codepath running with Burst. This means you won’t have to manually remove the [BurstCompile] attribute on codepath being debugged.
Modernizing the .NET runtime
The work involved in the migration to .NET CoreCLR runtime has already started, and it’s a very challenging journey. In order for us to successfully deliver this migration, we’d like to tackle the problem gradually and make sure that we can release pieces in a way that maintains stability of existing Unity projects.
So, we’re planning to deliver this migration in multiple phases:
Modernizing the Unity runtime
Improving the async/await programming model. Async/await is a fundamental programming approach to writing gameplay code that must wait for an asynchronous operation to complete without blocking the engine mainloop.
In 2011, before async/await was mainstream in .NET, Unity introduced asynchronous operations with iterator-based coroutines, but this approach is incompatible with async/await and can be less efficient. In the meantime, .NET Standard 2.1 has been improving the support of async/await in C# and .NET with the introduction of a more efficient handling of async/await operations via ValueTask, and by allowing your own task-like system via AsyncMethodBuilder.
We can now leverage these improvements, so we’re working on enabling the usage of async/await with existing asynchronous operations in Unity (such as waiting for the next frame or waiting for a UnityWebRequest completion). As a first step, we’re improving the support for canceling pending asynchronous tasks when a MonoBehavior is being destroyed or when exiting Play mode by using cancellation tokens. We have also been working closely with our biggest community contributors, such as the author of UniTask, to ensure that they will be able to leverage these new functionalities.
Reducing memory allocations and copies by leveraging Span<T>. Because Unity is a C++ engine with a C# Scripting layer, there’s a lot of data being exchanged between the two. This can be inefficient since it often requires either copying data back and forth or allocating new managed objects.
Span<T> was introduced in C# 7.2 to improve such scenarios and is available by default in .NET Standard 2.1. In recent years, you might have heard or read about many significant performance improvements made to the .NET Runtime thanks to Span<T> (see improvements details in .NET Core 2.1, .NET Core 3.0, .NET 6, .NET 6). We want to leverage its usage in Unity since this will help to reduce allocations and, consequently, Garbage Collection pauses while improving the overall performance of many APIs.
Join us on this journey
We hope that you’re all as excited as we are about these changes and features.
Let us know what you think about our plans on the forum. We’re also going to be regularly updating the engineering section of the Unity Platform Roadmap, and you can share your feature requests and prioritization suggestions with us there.