Memory efficiency is an important element of performance optimization. It's possible for games of limited scope, such as hobby projects and prototypes, to get away with ignoring memory management; they will tend to waste a lot of resources, and potentially leak memory, but this won't be a problem if we limit its exposure to friends and coworkers. However, anything we want to release professionally needs to take this subject seriously. Unnecessary memory allocations lead to poor user experience due to excessive garbage collection (costing precious CPU time), and memory leaks which will lead to crashes. None of these situations are acceptable in modern game releases.
Using memory efficiently with Unity requires a solid understanding of the underlying Unity Engine, Mono platform, the C# language. Also, if we're making use of the new IL2CPP scripting backend then it would be wise to become familiar with its inner workings. This can be a bit of an intimidating place for some developers since many pick Unity3D for their game development solution primarily to avoid the kind of low-level work that comes from Engine development and memory management. We'd prefer to focus on higher-level concerns related to gameplay implementation, level design, and art asset management, but, unfortunately, modern computer systems are complex tools and ignoring low-level concerns for too long could potentially lead to disaster.
Mono is a magical sauce, mixed into the Unity recipe, which gives it a lot of its cross-platform capability. Mono is an open source project that built its own platform of libraries based on the API, specifications, and tools from Microsoft's .NET Framework. Essentially, it is an open source recreation of the .NET Library, was accomplished with little-to-no access to the original source code, and is fully compatible with the original library from Microsoft.
The goal of the Mono project is to provide cross-platform development through a framework that allows code, written in a common programming language, to run against many different hardware platforms, including Linux, MacOS, Windows, ARM, PowerPC, and more. Mono even supports many different programming languages. Any language that can be compiled into .NET's Common Intermediate Language (CIL) is sufficient to integrate with the Mono platform. This includes C# itself, but also several other languages such as F#, Java, Visual Basic .NET, PythonNet, and IronPython. Of course, the only three exposed to us through Unity are C#, Boo, and UnityScript.
Note that the Boo language has already been deprecated in previous versions of Unity, and the UnityScript language will start becoming phased out in future versions. The blog post at https://blogs.unity3d.com/2017/08/11/unityscripts-long-ride-off-into-the-sunset/ explains the reasoning behind these changes.
A common misconception about the Unity Engine is that it is built on top of the Mono platform. This is untrue, as its Mono-based layer does not handle many important game tasks such as audio, rendering, physics and keeping track of time. Unity Technologies built a Native C++ backend for the sake of speed and allows its users control of this Game Engine through Mono as a scripting interface. As such, Mono is merely an ingredient of the underlying Unity Engine. This is equivalent to many other Game Engines, which run C++ under the hood, handling important tasks such as rendering, animation, and resource management, while providing a higher-level scripting language for Gameplay logic to be implemented. As such, the Mono platform was chosen by Unity Technologies to provide this feature.
Native Code is common vernacular for code that is written specifically for the given platform. For instance, writing code to create a window object or interface with networking subsystems in Windows would be completely different to code performing the tasks for a Mac, Unix, Playstation 4, XBox One, and so on.
Scripting languages typically abstract away complex memory management through automatic garbage collection and provide various safety features, which simplify the act of programming at the expense of runtime overhead. Some scripting languages can also be interpreted at runtime, meaning that they don't need to be compiled before execution. The raw instructions are converted dynamically into Machine Code and executed the moment they are read during runtime; of course, this often makes the code relatively slow. The last feature, and probably the most important one, is that they allow simpler syntax of programming commands. This usually improves development workflow immensely, as team members without much experience using languages such as C++ can still contribute to the code base. This enables them to implement things such as Gameplay logic in a simpler format at the expense of a certain amount of control and runtime execution speed.
Note that such languages are often called Managed Languages, which feature Managed Code. Technically, this was a term coined by Microsoft to refer to any source code that must run inside their Common Language Runtime (CLR) environment, as opposed to code that is compiled and run Natively through the target OS.
However, because of the prevalence and common features that exist between the CLR and other languages that feature their own similarly designed runtime environments (such as Java), the term Managed has since been hijacked. It tends to be used to refer to any language or code that depends on its own runtime environment and that may, or may not, include automatic garbage collection. For the rest of this chapter, we will adopt this definition and use the term Managed to refer to code that both depends on a separate runtime environment in order to execute and is being monitored by automatic garbage collection.
The runtime performance cost of Managed Languages is always greater than the equivalent Native Code, but it is becoming less significant every year. This is partly due to gradual optimizations in tools and runtime environments, and partly due to the computing power of the average device gradually becoming greater. Although, the main point of controversy with using Managed Languages still remains their automatic memory management. Managing memory manually can be a complex task that can take many years of difficult debugging to be proficient at, but many developers feel that Managed Languages solve this problem in ways that are too unpredictable, risking too much product quality. Such developers might cite that Managed Code will never reach the same level of performance as Native Code, and hence it is foolhardy to build high-performance applications with them.
This is true to an extent, as Managed Languages invariably inflict runtime overheads, and we lose partial control over runtime memory allocations. This would be a deal-breaker for high-performance server architecture; however, for game development, it becomes a balancing act since not all resource usage will necessarily result in a bottleneck, and the best games aren't necessarily the ones that use every single byte to their fullest potential. For example, imagine a user interface that refreshes in 30 microseconds via Native Code versus 60 microseconds in Managed Code due to an extra 100 percent overhead (an extreme example). The Managed Code version is still fast enough such that the user will never be able to notice the difference, so is there really any harm in using Managed Code for such a task?
In reality, at least for game development, working with Managed Languages often just means that developers have a unique set of concerns to worry about compared to Native Code developers. As such, choosing to use a Managed Language for game development is partly a matter of preference and partly a compromise of control over development speed.
Let's revisit a topic we touched upon in earlier chapters, but didn't quite flesh out: the concept of Memory Domains in the Unity Engine.
Memory space within the Unity Engine can be essentially split into three different Memory Domains. Each Domain stores different types of data and takes care of a very different set of tasks.
The first Memory Domain--the Managed Domain--should be very familiar. This Domain is where the Mono platform does its work, where any MonoBehaviour scripts and custom C# classes we write will be instantiated at runtime, and so we will interact with this Domain very explicitly through any C# code we write. It is called the Managed Domain because this memory space is automatically managed by a Garbage Collector.
The second Domain--the Native Domain--is more subtle since we only interact with it indirectly. Unity has an underlying Native Code foundation, which is written in C++ and compiled into our application differently, depending on which platform is being targeted. This Domain takes care of allocating internal memory space for things such as asset data (for example, textures, audio files, and meshes) and memory space for various subsystems such as the Rendering Pipeline, Physics System, and User Input System. Finally, it includes partial Native representations of important Gameplay objects such as GameObjects and Components so that they can interact with these internal systems. This is where a lot of built-in Unity classes keep their data, such as the Transform and Rigidbody Components.
The Managed Domain also includes wrappers for the very same object representations that are stored within the Native Domain. As a result, when we interact with Components such as Transform, most instructions will ask Unity to dive into its Native Code, generate the result there, and then copy it back to the Managed Domain for us. This is where the Native-Managed Bridge between the Managed Domain and Native...