Unit Testing

Advanced Unity Debugging with log4net

This tutorial will teach you how to use log4net to write logs to multiple sources in Unity.

Logging is an important part of programming, even in game development. The Apache Software Foundation’s website claims that approximately 4% of all code is dedicated to it, and it’s easy to see why.

Apart from helping you chase down bugs, logs can also serve as a form of documentation. Programs that log their every move are simultaneously creating a written record of how they function. This information is invaluable to understanding what your code is doing at runtime.

But the best logs in the world are utterly useless if you can’t find them. That’s where the ability to write your logs to multiple sources comes in handy.

The Apache log4net Library

There are plenty of tools that can write logs to more than one source. However, this tutorial will focus on log4net.

The Apache log4net library is a logging tool that gives you control over how and where your log statements are written. It’s a popular choice among developers because it’s open source and has great community support.

Out of the box, log4net can write logs to a variety of useful sources, such as rolling logs and databases. But to get it to work in Unity you’ll need to show it how to write to Unity’s built-in Debug class. That’s what this tutorial will cover

Tutorial

Step 1 – Dependencies

Download the latest log4net DLL from https://logging.apache.org/log4net.

Then import the DLL into your Unity project. Make sure you select the DLL that’s compatible with the Scripting Runtime Version of your project.

Step 2 – Custom Appender

Log4net doesn’t know how to write logs to the Unity console out of the box. You’ll need to show it how using a custom Appender.

Copy the code below and add it to your project.

Step 3 – Configure Logging With log4net

Now that you have a custom Appender you’ll need to configure log4net to use it. A common practice among developers is to use an XML file to set everything up, so go ahead and add the following file to your Unity project’s Asset folder.

For more on configuring log4net visit https://logging.apache.org/log4net/release/manual/configuration.html.

Step 4 -Initialize Logging

Log4net must be initialized with the XML config file before it’ll begin writing logs. You have to be sure to do this before any logs are created.

You can do this in Unity using the RuntimeInitializeOnLoadMethod attribute. This attribute triggers the method that it’s marking to be executed either right before or after the scene is loaded.

Copy the code below and add it to your project.

The configure method calls log4net’s built in XmlConfigurator and passes in the path to the config file that you created in step 3.

Step 5 – Start Logging!

Congratulation, you’ve successfully added log4net to your Unity project. Begin logging by creating an instance of ILog using the LogManager. A common practice is to add a private static readonly ILog field to each class.

Passing in the class type into GetLogger provides the log messages context about where they came from.

Downloads

Summary

Logging is an important part of programming, even in game development. One of my biggest game programming tips is to make sure your that all of your projects have some sort of log to file functionality.

With log4net you can write log statements to multiple sources, making it a powerful tool for debugging in Unity.

Unity

Unity Job System Explained

There’s been a lot of buzz surrounding the Unity Job System over the past couple of years. But a lot of Unity developers still don’t seem to understand what it is or how it can benefit them.

In this post, I’m going to shed a little light on what the Unity Job System is, how it can help you, and what it looks like in practice.

Unity Capsicum: Performance by Default

Unity 2018 came with a new set of features that introduced the concept of performance by default. Code named “Capsicum”, this feature set includes the Unity Job System, Unity ECS, and the Burst Compiler.

When used correctly, Capsicum dramatically improves the performance of your game code.

But this post is about the Unity Job System. So, what is it? Well, at a high level, the Unity Job System allows you to introduce multithreading to your game code.

Improving Performance With Multithreading

Multithreading is a type of programming that takes advantage of a CPU’s capacity to process many threads at the same time across multiple cores.

A thread contains all of the contextual data needed to execute a set of instructions.

Traditionally, applications run all of their code on a single thread, called the “main thread”. But with multithreading you can write applications that execute multiple sets of instructions concurrently, or at the same time.

How Does Multithreading Improve Performance?

Multithreading is a key ingredient in improving the performance of your game code.

By splitting up CPU intensive operations into multiple processes that can all run at the same time, you can dramatically reduce how long those operations takes to execute. This results in massive improvements to loading times, frame rates, and battery life.

So, why do a lot of developers seem to avoid multithreaded code like the plague?

Why is it so Hard to Write Multithreaded Code?

Multithreaded code is complex and error-prone. For one, it’s easy to let your thread count get out of control, which can cause frequent context switching.

Context Switching

A Context Switch is a procedure that the CPU follows to change from one task to another. This happens automatically and it plays an important role in your computer’s ability to multitask.

During a context switch, the CPU has to store the state, or context, of the running task. This information is important because it helps the CPU avoid conflicts and also to resume the task later.

The problem is that context switches are expensive. And the time it takes to perform them adds up quickly. This becomes more of a problem as the number of active threads approaches and exceeds the number of available cores.

Race Condistions

Another complication of multithreaded code is the possibility of race conditions.

A race condition is a situation where your code behaves differently depending on the order in which it executes.

You can start threads in any order you want, but there’s no way to tell how they’ll run in relation to one another or when each one will finish finish. This indeterminism can result in unpredictable behavior and errors that are hard to debug.

This is especially true if two or more threads share and modify the same data.

Imagine a room with multiple light switches that all affect the same light bulb. If multiple people enter the room and randomly toggle light switches throughout the day, then it’s hard to predict whether the light will be on or off at any given moment.

On top of these examples, there are plenty of other factors that make writing multithreaded code a complex and difficult task. And that is where the Job System comes in.

The Unity Job System

The Unity Job System allows you to write multithreaded code easily and safely. It does this by creating jobs instead of threads.

A Job represents a units of work that the system can process as a series of steps.

When you a schedule job, the system places it into a special queue. Then, at some point during execution, a worker thread will pull the job out of the queue and execute it.

Worker threads are individual threads that are managed by the Unity Job. They complete jobs in the background so they don’t interrupt the main thread.

All you need to do is place your logic into custom jobs and then schedule them to run. The Job System will handle the rest, executing all of your jobs on seperate threads that are managed by Unity.

Why not Write Your own Multithreaded Code?

So how is this better than writing your own multithreaded code? I mean, under the covers, the Job System is still creating threads right? It’s still susceptible to all of the difficulties associated with multithreading.

The difference is that the developers who created the Unity Job System have gone to great lengths to ensure that their multithreaded code is iron clad.

For instance, the Job System will do it’s best to avoid context switching by only creating one worker thread per logical CPU core. This allows you to create as many jobs as you want (within reason) without having to worry about how it’ll affect the performance of your CPU.

The Job System also has a built-in mechanism for guarding against race conditions in the form of job dependencies. For example, if Job A needs to prepare some data for Job B, you can assign it as Job B’s dependency. That way Job A will always run first, and Job B will always have the correct data.

So what does a job look like and how do you schedule one? Let’s take a quick look at an example from Stalla3D’s Job System Cookbook Github Repo.

Using the Unity Job System to Modify a Mesh

In this example, the job uses Perlin Noise to modify the vertices and normals of a mesh during each frame. The job takes an array of vertices and normals, and floats that represent sine time, cosine time, and strength.

It’s execute method runs for each vertex in the mesh, and its corresponding normal. All it really does is calculate a new vertex and normal for the given index. And this is where the power of the Job System lies.

When this job is scheduled, Unity will queue up as many instances of it as it needs and the Worker threads will begin to execute them in parallel. The more logical cores your CPU has, the less time it’ll take for this job to complete.

I’ll go over an example in more detail in another post, but you can see by the Update method that scheduling this job is extremely simple. And the overall code footprint is much easier to understand and safer to write than low level multithreaded code would be.

Summary

So that’s the Unity Job System at a high level. It allows you to use multithreading in a way that safe, easy, and completely managed by Unity.

And when you combine it with Unity ECS and the Burst Compiler, you get extremely performant code that’s optimized by default.