Currently many mature libraries and frameworks exist for each programming language and many advanced features were added to the languages. But what about the old projects where the language features were not advanced as now and no many mature libraries existed yet?
Let’s explore some old well implemented projects and discover how they are implemented.
Prince of Persia
Prince of Persia is a fantasy platform game, originally developed by Jordan Mechner and released in 1989 for the Apple II, that represented a great leap forward in the quality of animation seen in video games. On Apr 17, 2012 Jordan Mechner released the source code of Prince of Persia.
Many gamers remember this amazing game, and maybe some of you played with it for many months.
The developers of these days remember there was a time when a typical personal computer might have an 8 Mhz processor, 1 megabyte of memory, a 20 megabyte hard disk, and a floppy disk drive. It was a big challenge to develop a game like Prince of Persia.
Moreover in those days, Google was not there to help developers resolve their technical issues quickly; some technical problems could take the developers many days to fix them. And the cherry on the cake was developed using 6502 assembly language.
However, with all these constraints, the source code is well implemented:
- It’s modularized using directories and files:
Modularity is a software design technique that increases the extent to which software is composed from separate parts, you can manage and maintain modular code easily.
Prince of Persia was modularized using directories and files, this modularity is provided by the operating system and can be applied to any language.
The code is split into many files, here’s a list of some of them:
- The naming is easy to understand
When exploring the source code, you don’t find variable names such as a, b or x, like many recent developed projects. The names are well chosen and no need to comments to explain why we need them.
- The code is split into many small subroutines
The 6502 assembly language is very low level, and to make the code easier to understand and maintain, the “Divide and Conquer” principle is applied. Indeed the code is split into many small subroutines, what makes them easy to read and maintain. Here’s an example of a small subroutine defined in its source code:
Even If in 1989 many constraints complicated the task for developers, the code is very well implemented. So why in 2014 with powerful computers, powerful languages, Many thousands of libraries and Google, some projects are bad implemented ?
Languages and frameworks are just tools to build applications, but the main actor is the developer. You can use the best language, the best frameworks and produce a bad code.
Many practices to make the code clean are not language dependent. A good developer must have the good sense and make its code clean and easy to understand whatever the language used.
Doom 3 is a video game developed by id Software and published by Activision. The game was a commercial success for id Software; with more than 3.5 million copies of the game were sold.
On November 23, 2011 id Software maintained the tradition and it released the source code of their previous engine. This source code was reviewed by many developers, here’s as example the feedback from fabien (orginal source):
Doom 3 BFG is written in C++, a language so vast that it can be used to generate great code but also abominations that will make your eyes bleed. Fortunately id Software settled for a C++ subset close to “C with Classes” which flows down the brain with little resistance:
- No exceptions.
- No References (use pointers).
- Minimal usage of templates.
- Const everywhere.
Many C++ experts don’t recommend any more the “C with classes” approach. However, Doom3 was developed between 2000 and 2004, what could explains the not use of the modern C++ mechanisms.
Let’s go inside its source code using CppDepend and discover what makes it so special.
Doom3 is modularized using few projects, here’s the list of its projects, and some statistics about their types:
And here’s the dependency graph to show the relation between them:
Doom3 defines many global functions. However, most of the treatments are implemented in classes.
The data model is defined using structs. To have a concrete idea of using structs in the source code, the metric view above shows them as blue rectangles.
In the Metric View, the code base is represented through a Treemap. Treemapping is a method for displaying tree-structured data by using nested rectangles. The tree structure used is the usual code hierarchy:
- Project contains namespaces.
- Namespace contains types.
- Type contains methods and fields.
As we can observe many structs are defined, for example more than 40% of DoomDLL types are structs. They are systematically used to define the data model. This practice is adopted by many projects, this approach has a big drawback in case of multithreaded applications. Indeed, structs with public fields are not immutable.
There is one important argument in favor of using immutable objects: It dramatically simplifies concurrent programming. Think about it, why does writing proper multithreaded programming is a hard task? Because it is hard to synchronize threads access to resources (objects or others OS resources). Why it is hard to synchronize these accesses? Because it is hard to guarantee that there won’t be race conditions between the multiple write accesses and read accesses done by multiple threads on multiple objects. What if there are no more write accesses? In other words, what if the state of the objects accessed by threads, doesn’t change? There is no more need for synchronization!
Let’s search for classes having at least one base class:
Almost 40% of stucts and classes have a base class. And generally in OOP one of the benefits of inheritance is the polymorphism, here are in blue the virtual methods defined in the source code:
More than 30% of methods are virtual. Few of them are virtual pure and here’s the list of all abstract classes defined:
Only 52 are defined abstract classes, 35 of them are defined as pure interfaces,i.e. all their virtual methods are pure.
Let’s search for methods using RTTI
Only very few methods use RTTI.
To resume only basic concepts of OOP are used, no advanced design patterns used, no overuse of interfaces and abstract classes, limited use of RTTI and data are defined as structs.
Until now nothing special differentiate this code from many others using “C with Classes” and criticized by many C++ developers.
Here are some interesting choices of their developers to help us understand its secret:
1 – Provides a common base class with useful services.
Many classes inherits from the idClass:
The idClass provides the following services:
- Instance creation.
- Type info management.
- Event management.
2- Make easy the string manipulation
Generally the string is the most used type in a project, many treatments are done using them, and we need functions to manipulate them.
Doom3 defines the idstr class which contains almost all useful methods to manipulate strings, no need to define your own method as the case of many string classes provided by other frameworks.
3- The source code is highly decoupled with the GUI framework (MFC)
In many projects using MFC, the code is highly coupled with their types, and you can find types from MFC everywhere in the code.
In Doom3, The code is highly decoupled with MFC, only GUI classes has direct dependency with it. As shown by this following CQLinq query:
This choice has a big impact on the productivity. Indeed, only the Gui developers must care about the MFC framework, and for the other developers it’s not mandatory to waste time with MFC.
4- It provides a very good utility library (idlib)
In almost all projects the most used types are utility classes, as shown by the result of this following query:
As we can observe the most used are utilities ones. If C++ developers don’t use a good framework for utilities, they spend most of their development time to fight with the technical layer.
idlib provides useful classes with all needed methods to treat string, containers, and memory. Which facilitate the work of developers and let them focus more on the game logic.
5- The implementation is very easy to understand
Doom3 implements a hard coded compiler, and as known by C++ developers, it’s not an easy task to develop parsers and compilers. However, the implementation of the Doom3 is very easy to understand and its code is very clean.
Here’s the dependency graph of the classes used by the compiler:
And here’s a code snippet from the compiler source code:
We already study the code source of many parsers and compiler. But it’s the first time we discover a compiler with a source code very easy to be understood, it’s the same for the whole Doom3 source code. It’s magic. When we explore the Doom3 source code, we can’t say: WOW it’s beautiful!
Even if the Doom3 design choices are very basic, but its designers make many decisions to let developers focus more on the game logic, and facilitate all the technical layer stuff. Which increase a lot the productivity.
However when using “C with Classes”, you have to know exactly what you are doing. You have to be expert like Doom3 developers. It’s not recommended for a beginner to take risk and ignore the Modern C++ recommendations.