Alternative Version









November 15, 2021

Klang: Doom (#002)


Less is More!!!






This is the second blog post about my project called Klang. Here is the link if you didn't read the first post about that: "Klang: Wolf3d (#001)".

This post will focus on "Less is More" which is having just the essential things is better than having too much of superfluous things.

Like the first post, I used a real case which is the first Doom (1993) so let's begin!


Alias



typedef is an old keyword from C and acts like an alias: typedef source ALIAS;
It doesn't create a new type (just an alias) and it is still used in C++. Why? Retro-compatibility!

Here are some examples:
typedef int HealthPoint;
typedef unsigned long long int u64;
typedef Node* NodePtr;
typedef void (*SignalHandler)(int);

With Modern C++ 11, the keyword using got an update and now can act like typedef and even replace this one (and more). If you ever used C++ with STL, you probably know the meaning of "using namespace ..." or the image below looks familiar:





Now, let's use using like typedef from the examples above:
using HealthPoint = int;
using u64 = unsigned long long int;
using NodePtr = Node*;
using SignalHandler = void (*)(int);

Better readability, specially for the function pointer (functor).

For Klang, I prohibited the usage of typedef so if we try to compile with it, it will generate an error:





There are 6340 typedef inside the Doom Retro project. A lot inside the SDL library which also uses OpenGL. I also updated the Windows SDK files which contain a lot of typedef (compatibility with C and old C++) to be able to compile with Klang.

The good thing is llvm/clang provide a tool to modernize your C++ called clang-tidy.

I used clang-tidy to replace typedef by using on Doom, SDL/OpenGL and Windows SDK source files. It helped but some replacements stripped the code and generated a bad syntax. I manually updated those parts.


Oh Long Johnson!



The previous post, I switched the floating point primitives: float -> f32 and double -> f64. This part is about the other primitives: short, int, long, signed, unsigned, ...

The following table summarizes all primitives / types in C++:
Type / Primitive Size in bits Value Range
char
signed char
8 -128
...
127
unsigned char 8 0
...
255
short
short int
signed short
signed short int
16 -32,768
...
32,767
unsigned short
unsigned short int
16 0
...
65,535
int
signed
signed int
long
long int
signed long
signed long int
32 -2,147,483,648
...
2,147,483,647
unsigned
unsigned int
32 0
...
4,294,967,295
long long
long long int
signed long long
signed long long int
64 -9,223,372,036,854,775,808
...
9,223,372,036,854,775,807
unsigned long long
unsigned long long int
64 0
...
18,446,744,073,709,551,615
You can declare variables in multiple ways in C++ which in my opinion adds confusion. The main reason is to keep compatibility with 16 bits and 32 bits environments (we work in 64 bits now).

There are 7 different ways for a simple integer (32 bits). We need to write 3-4 words to declare an unsigned 64 bits variable... Even Modern C++ 11 added "unsigned long long int" and "long long int".

Maybe people that created this semantic based their idea on the numbers in french language:

For Klang, I applied the same pattern that I applied for floating point primitives and here is the result:
Type / Primitive Size in bits Value Range
char
s8
8 -128
...
127
u8 8 0
...
255
s16 16 -32,768
...
32,767
u16 16 0
...
65,535
s32 32 -2,147,483,648
...
2,147,483,647
u32 32 0
...
4,294,967,295
s64 64 -9,223,372,036,854,775,808
...
9,223,372,036,854,775,807
u64 64 0
...
18,446,744,073,709,551,615
Cleaner, isn't it?

I kept the "char" for the text/string and I may revisit later for next posts. Now, it's 3 letters instead of 3-4 words to declare an unsigned 64 bits variable.

I could use using to create an alias but my goal is to trim C++ and create a new version which by time will not be compatible with C++.


Doom






Above is a screenshot from the book Game Engine Black Book: Doom of Fabien Sanglard. His second book is about the history and engineering of the first Doom and how they (id) did it. An excellent book if you like Doom, coding, graphics, ...

After the 2 major modifications (alias and primitives renaming), here is the new look of the Doom source code [before/after]:



For the source code, I didn't use the original one (on DOS), I cloned the Doom Retro repository.
"DOOM Retro is the classic, refined DOOM source port for Windows PC. It represents how I like my DOOM to be today, in all its dark and gritty, unapologetically pixelated glory. I have strived to craft a unique ensemble of compelling features, while always respecting that classic, nostalgic DOOM experience many of us still hold dear."

The code base is in C but it was easy to port with Klang. I did the same update that I did on Wolfenstein-3d plus the modification above:
- Removed all goto instructions
- Removed some tabs (e.g. WinSDK)
- Renamed C files into C++ (Need to find a file extension for Klang)
- Replaced typedef by using
- Replaced types / primitives: unsigned char, short, int, long, unsigned, signed, ... -> s8, u8, s16, u16, s32, u32, s64, u64, f32, f64
- Added curly braces if needed
- Build script with copy dependencies (dll) / data (wad)

The repository doesn't provide the data from Doom (which will be illegal) but I bought a Doom package on Steam a couple of years ago so I added Ultimate Doom and Doom 2 data.

Here is the result:

With this build, I finished the three first episodes of Doom and tried a little bit of Doom 2. That was fun! I also applied the new Klang on Wolfenstein-3d (using and primitives).

Thanks for reading,

JS.