Logs have levels: debug, warning, error, etc. So why are all runtime asserts on just one level? Today’s article provides some code that allows you to add levels to your asserts based on how fast they are: fast, normal, slow, super slow. It also shows how to use these levels to balance between performance and safety.

This article is also inspired by a CppCon talk by John Lakos. In the talk he describes the system of asserts used at his his company, to much success. In that system there are levels of asserts categorized by how fast the assert is:

  • ASSERT_OPT — <5% overhead
  • ASSERT — 5-20% overhead
  • ASSERT_SAFE — 20-100% overhead
  • ASSERT_SAFE_2 — >100% overhead

I’ve renamed these as follows:

  • AssertFast — <5% overhead
  • AssertNormal — 5-20% overhead
  • AssertSlow — 20-100% overhead
  • AssertSuperSlow — >100% overhead

Each of these is a class that you can use exactly like Unity’s Assert. In fact, it’s basically a direct copy of Unity’s Assert with only a few minor tweaks. The critical tweak is to change the [Conditional] attribute that controls whether nor not calls the assert functions are left in or taken out by the compiler. For more on how to do that, check out this article.

Normally, Unity’s asserts are left in by the compiler only when UNITY_ASSERTIONS is set. This is the case for development builds and when you use BuildOptions.ForceEnableAssertions. Likewise, this is the case for AssertFast, AssertNormal, AssertSlow, and AssertSuperSlow.

The tweak is that you can disable individual assert levels rather than all or none of them. You do that by setting these “Scripting Define Symbols” in “Project Settings”:

  • UNITY_ASSERTIONS_DISABLE_FAST
  • UNITY_ASSERTIONS_DISABLE_NORMAL
  • UNITY_ASSERTIONS_DISABLE_SLOW
  • UNITY_ASSERTIONS_DISABLE_SUPER_SLOW

Each assert function in each of these assert classes uses a combination of #if and [Conditional] to respect those scripting define symbols:

#if UNITY_ASSERTIONS_DISABLE_SUPER_SLOW || !UNITY_ASSERTIONS
[Conditional("__NEVER_DEFINED__")]
#endif
public static void IsTrue(bool condition)
{
	// ...
}

I’ve created a small GitHub project with the files you need to add this to your project. Just copy them to your Assets directory and you should be all set!

Now that we have this capability, let’s see how to use it. It’s really easy and mostly follows the steps in the previous article. The only difference is that you categorize your asserts by roughly how much overhead they’re going to impose on the normal code. For example, say we have a function that does a binary search on an IList to find a value:

public int BinarySearch<T>(IList<T> list, int startIndex, int endIndex)
{
	// Fast check (<5% overhead because this just does one trivial check)
	AssertFast.IsTrue(list != null);
 
	// Normal check (5-20% overhead because this does several checks)
	AssertNormal.IsTrue(
		startIndex >= 0
		&& startIndex < list.Count
		&& endIndex >= 0
		&& endIndex < list.Count
		&& startIndex <= endIndex
	);
 
	// Super slow check (100%+ overhead because this does an O(N) check for a function that's O(log(N)))
	AssertSuperSlow.IsTrue(IsSorted(list));
 
	// ... function logic ...
}

In this example we used three different levels of asserts in one function. If you don’t care at all about the game’s performance then you might want to leave all the assert levels on. If the game’s performing too slowly for you to test something out, you have the option to turn off just the slower assert levels by simply defining the UNITY_ASSERTIONS_DISABLE_SLOW and UNITY_ASSERTIONS_DISABLE_SUPER_SLOW scripting define symbols. If it’s still too slow, go ahead and define UNITY_ASSERTIONS_DISABLE_NORMAL or even UNITY_ASSERTIONS_DISABLE_SUPER_FAST.

This gives you a “dial” that you can turn to balance between safety and performance. On the safety side you get the full error-checking of every assert in the codebase. On the performance side you’ve turned off all the asserts that they’ll all be stripped out of the build. In between you can pick and choose exactly which assert levels you want on at any given time.

Hopefully you’ll find assert levels useful in your projects!