Lessons to learn from Oculus development team when using the “Modern C++” approach.

In the previous post about Doom3, we discovered that it was developed using the “C with Classes” approach. John Carmack is the main developer and it was between 2000 and 2004, what explains why “Modern C++” approach was not adopted. And, even if “C with Classes” is not recommended, he made some good decisions to have clean code.

John Carmack is now the oculus VR CTO, Oculus VR is an American virtual reality technology company founded by Palmer Luckey and Brendan Iribe. Their first product, still in development, is the Oculus Rift, a head-mounted display for immersive virtual reality (VR).

Oculus provides an SDK which includes its source code, it’s developed using C++.  For this project the Modern C++ approach was adopted. Let’s discover some design and implementation choices of its primary architect Michael Antonov.

1- Use namespaces to modularize the SDK

Namespaces were introduced to the C++ Standard in 1995 and usually they are most often used to avoid naming collisions. Although namespaces are used extensively in recent C++ code, most older code does not use this artifact.
Namespaces are useful to modularize your projects, to make it more readable and maintainable. Here’s the list of all namespaces defined in the oculus SDK:
ovr1

2- Minimize the use of global variables and functions

The SDK defines few global functions; most of them are the exported ones. Even if a function is just an utility, it’s better to add it in a  class as static to group utilities by category.

3- Modern C++ doesn’t imply necessarly the overuse of templates

Andrei Alexandrescu says about the Modern C++ design:

Modern C++ Design defines and systematically uses generic components – highly flexible design artifacts that are mixable and matchable to obtain rich behaviors with a small, orthogonal body of code.

Modern C++ has a close relation with generic programming; probably it’s the reason that makes many developers neglect the modern C++ approach. They think that the code will be mostly implemented as templates, which makes the code difficult to read and maintain.

In the SDK, the templates represent only 20% of all types defined and most of them are related to the technical layer.

The following CQLinq query give us the types defined by the SDK:

ovr3

 

It’s true that some Modern C++ libraries like Boost mainly define their classes as templates to be as the most generic as possible. However, in many other applications it’s not necessarily the case. You could use templates when the genericity is suitable and needed.

4- Don’t repeat yourself

Repeating boilerplate code makes maintenance difficult, and allows all kinds of mistakes. With generic programming you can avoid harmful repetition. Think about using generic everywhere, the code is similar and differs only in types manipulated.

The SDK defines a templated singleton, to avoid the same singleton code in many places:

ovr5

5- Allocation and RAII

RAII is a C++ idiom which binds the life cycle of a resource  to the lifetime of an object with automatic storage duration, which guarantees that all resources are cleaned up when the object goes out of scope, in reverse order of acquisition. This leverages the language to eliminate resource leaks and guarantee exception safety.

To manage allocation, the oculus development team defines its reference counted class OVR::RefCountBase which is a base class for classes that require thread-safe reference counting; it also overrides the new and delete operators to use MemoryHeap.

Many classes inherit from it to manage their life cycle.

ovr8

These ref counted classes are used through the Reference Counted template pointer OVR::Ptr<C> which contains the logic of AddRef and Release methods.

With these allocation utility classes, the SDK users will not care about releasing the pointers used, or spending time to resolve memory management problems due to the use of multithreading. The classes implemented by the SDK free the developers from the memory management problems.

6- Prefer static polymorphism to dynamic polymorphism

In classic C++, the dynamic polymorphism is generally overused, which implies the use of inheritance, and the use of the virtual table at runtime to know the method of each child class to invoke. On the other side, static polymorphism is completely resolved at compile time.

For example the SDK defines the Shader class for Direct 3D version 10, Direct3D version 11 and OpenGL.

ovr9

In contrast to  classic C++  they do not have a common base class, so the RTTI could not be used to decide the methods of which one will be used at runtime.

Here’s a snippet of the implementation of SharedImpl class. This class has two template parameters and  the decision of which Shader to use is done at compile time by specifying the D3DShaderType when an instance is created.

ovr17

In Modern C++, static polymorphism is preferred to the dynamic one, it makes the code more flexible, no need to have an inheritance relation between classes, which enforce the low coupling.

7- Separate data model from treatments

The oculus SDK defines many structs for its data model, here is the CppDepend metric view which shows in blue all of them.

ovr2

Let’s take as example ovrSensorData struct from the SDK, and suppose it was defined as class containing also the treatments. If we need to use these data in another context that we need to add other methods, we will have a not cohesive class after few evolutions.

8- Use some most common modern C++ techniques 

The Modern C++ design introduced many idioms, some of them are easy to understand and implement, others are difficult and makes the code not easy to read and maintain.

You can focus only on the most common used ones; the oculus SDK uses mainly Traits and Policy-based design techniques. Here’s an example of a class from the SDK using them:

ovr10

Don’t force yourself to use all the Modern C++ idioms like SFINAE or other advanced ones, unless you really need to.

9- It’s not mandatory to use Exception handling

Even if many Modern C++ resources recommend the use of exceptions they are not used in many known open source projects. The exceptions provide a powerful mechanism to report errors. However, it’s not easy to write an exception safety code.

The oculus SDK developers prefer to not use them.

10- And specially don’t forget to provide recurrent technical layer needs

The classes related to the technical layer are generally the most used ones as shown by this following CQLinq query:

ovr18

In some projects the C++ developers  spend most of their development time fighting with the technical layer.

As the decision taken for Doom3, the oculus SDK provides all needed low level classes, powerful String and StringBuilder classes are implemented, many useful classes concerning multithreading, memory management, containers are provided, which makes the task very easy for the SDK users.

Summary:

In the oculus SDK source code and as Doom3,  the priority is to make the life easy for developers by:

  • Providing a pragmatic and complete framework for technical layer.
  • Leting the developers focus more on the application logic.
  • Having a compact and very clean code.

To resume it’s better to study a source code using the Modern C++ approach instead of reading only books and articles. The oculus SDK is a good candidate; it’s not a big project and it provides some useful Modern C++ best practices.

Leave a Reply

Your email address will not be published.