Spring is a free open source RTS game engine originally created by Stefan Johansson and Robin Westberg, members of the Swedish Yankspankers game clan. Originally intended to bring the gameplay experience of Total Annihilation into three dimensions, the engine has since evolved to support a plethora of more modern and more flexible features, including built-in high-level extensibility through a Lua scripting interface.
Any developer who wants to contribute to Spring RTS or any other open source project needs to understand the existing code, and for that he can read the documentation, explore forums, read comments from code or read the code itself.
Another way is to use tools and query the code base. Like SQL for a relational database, CppDepend provides the CQLinq language to request the code and help to understand C/C++ within. It’s also free for open source projects.
In this article we will analyze Spring RTS with CppDepend to discover some internal design choices.
C++ is not just an object-oriented language. As Bjarne Stroustrup points out, “C++ is a multi-paradigmed language.” It supports many different styles of programs, or paradigms, and object-oriented programming is only one of these. Some of the others are procedural programming and generic programming.
Global functions
Let’s search for all global functions inside the Spring engine:
5092 functions are global, which represents around 20% of all the Spring methods. But what’s interesting is that these functions are grouped by namespaces which makes the code modular and easy to understand.
Structures
In a procedural program, modules interact by reading and writing a state that is stored in shared data structures. Let’s search for all data structures defined by Spring:
Static functions
In general, it’s better to declare a function as static unless you have a specific need to call it from another source file.
Functions candidate to be static
The object-oriented paradigm makes extensive use of virtual functions and polymorphism, let’s search for virtual methods.
And to have a better idea of existing virtual methods, we can use the Metric View.
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 in a CppDepend treemap is the usual code hierarchy:
As we can observe, virtual methods are not widely used.
Abstract classes
Low coupling is desirable because a change in one area of an application will require less changes throughout the entire application. In the long run, this could alleviate a lot of time, effort, and cost associated with modifying and adding new features to an application. Using abstract classes can improve the low coupling; let’s search for all abstract classes:
Inheritence
In object-oriented programming (OOP), inheritance is a way to establish Is-a relationship between objects. It is often confused as a way to reuse the existing code which is not a good practice because inheritance for implementation reuse leads to Tight Coupling. Re-usability of code is achieved through composition (Composition over inheritance). Let’s search for all classes having at least one base class:
The Metric view shows us that many classes are concerned, and inheritance is mainly used.
Generic Programming
C++ provides unique abilities to express the ideas of Generic Programming through templates. Templates provide a form of parametric polymorphism that allows the expression of generic algorithms and data structures. The instantiation mechanism of C++ templates insures that when a generic algorithm or data structure is used, a fully-optimized and specialized version will be created and tailored for that particular use, allowing generic algorithms to be as efficient as their non-generic counterparts.
In the C++ world two schools are very popular: Object Oriented Programming and Generic Programming, each approach has their advocates, this article explains the tension between them. Let’s search if Spring uses templates:
Spring RTS uses template classes, especially for containers and utility classes. We can also search for generic methods:
Only some methods are templated.
To understand what happens when the RTS engine starts, let’s begin with the dependency graph concerning some methods invoked by the entry point WinMain.
When the engine starts it searches for the existing cores to exploit all the processing machine power, and after that delegates the initialization to the SpringApp class. Let’s search what happens when the Initialize method is invoked.
This method initializes all the other classes with which it collaborates like LuaOpenGL, CMyMath and CGlobalRendering, and after it invokes the startup method. And here’s the dependency graph of the startup method.
When developing a product, it’s preferable to not reinvent the wheel and reuse proven and mature frameworks. Let’s search for external types used by Spring.
Spring RTS uses mainly boost, this library is aimed at a wide range of C++ users and application domains. They range from general-purpose libraries like the smart pointer library, to operating system abstractions like Boost FileSystem, to libraries primarily aimed at other library developers and advanced C++ users, like the template metaprogramming (MPL) and domain-specific language (DSL) creation (Proto).
For example concerning network communication Spring uses the boost.asio library, which provides developers with a consistent asynchronous model using a modern C++ approach.
For any developer using the Spring RTS, or any new code base, it’s better to know sometimes how it works internally, for which the better approach is to go inside the code source. In this case we have found that the Spring RTS code is very simple to understand and evolve.
Any developer who wants to contribute to Spring RTS or any other open source project needs to understand the existing code, and for that he can read the documentation, explore forums, read comments from code or read the code itself.
Another way is to use tools and query the code base. Like SQL for a relational database, CppDepend provides the CQLinq language to request the code and help to understand C/C++ within. It’s also free for open source projects.
In this article we will analyze Spring RTS with CppDepend to discover some internal design choices.
Paradigm used
C++ is not just an object-oriented language. As Bjarne Stroustrup points out, “C++ is a multi-paradigmed language.” It supports many different styles of programs, or paradigms, and object-oriented programming is only one of these. Some of the others are procedural programming and generic programming.
Procedural Paradigm
Global functions
Let’s search for all global functions inside the Spring engine:
from m in Methods where m.IsGlobal && !m.IsThirdParty select m
5092 functions are global, which represents around 20% of all the Spring methods. But what’s interesting is that these functions are grouped by namespaces which makes the code modular and easy to understand.
Structures
In a procedural program, modules interact by reading and writing a state that is stored in shared data structures. Let’s search for all data structures defined by Spring:
from t in Types where t.IsStructure && t.Methods.Count()==0 select t
Static functions
In general, it’s better to declare a function as static unless you have a specific need to call it from another source file.
from m in Methods where m.IsGlobal && m.IsStatic
Functions candidate to be static
from m in Methods where m.IsGlobal && !m.IsStatic && !m.IsThirdParty && m.MethodsCallingMe.Where(a=>a.SourceDecls.FirstOrDefault()!=null && a.SourceDecls.FirstOrDefault().SourceFile.FilePathString!=m.SourceDecls.FirstOrDefault().SourceFile.FilePathString).Count()==0 select m
Object Oriented paradigm
The object-oriented paradigm makes extensive use of virtual functions and polymorphism, let’s search for virtual methods.
from m in Methods where m.IsVirtual select m
And to have a better idea of existing virtual methods, we can use the Metric View.
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 in a CppDepend treemap is the usual code hierarchy:
- Projects contains namespaces.
- Namespaces contains types.
- Types contain methods and fields.
As we can observe, virtual methods are not widely used.
Abstract classes
Low coupling is desirable because a change in one area of an application will require less changes throughout the entire application. In the long run, this could alleviate a lot of time, effort, and cost associated with modifying and adding new features to an application. Using abstract classes can improve the low coupling; let’s search for all abstract classes:
from t in Types where t.IsAbstract select t
Inheritence
In object-oriented programming (OOP), inheritance is a way to establish Is-a relationship between objects. It is often confused as a way to reuse the existing code which is not a good practice because inheritance for implementation reuse leads to Tight Coupling. Re-usability of code is achieved through composition (Composition over inheritance). Let’s search for all classes having at least one base class:
from t in Types where t.BaseClasses.Count()>0 select t
The Metric view shows us that many classes are concerned, and inheritance is mainly used.
Generic Programming
C++ provides unique abilities to express the ideas of Generic Programming through templates. Templates provide a form of parametric polymorphism that allows the expression of generic algorithms and data structures. The instantiation mechanism of C++ templates insures that when a generic algorithm or data structure is used, a fully-optimized and specialized version will be created and tailored for that particular use, allowing generic algorithms to be as efficient as their non-generic counterparts.
In the C++ world two schools are very popular: Object Oriented Programming and Generic Programming, each approach has their advocates, this article explains the tension between them. Let’s search if Spring uses templates:
from t in Types where t.IsGeneric && !t.IsThirdParty select t
Spring RTS uses template classes, especially for containers and utility classes. We can also search for generic methods:
from m in Methods where m.IsGeneric && !m.IsThirdParty select m
Only some methods are templated.
Spring RTS startup
To understand what happens when the RTS engine starts, let’s begin with the dependency graph concerning some methods invoked by the entry point WinMain.
When the engine starts it searches for the existing cores to exploit all the processing machine power, and after that delegates the initialization to the SpringApp class. Let’s search what happens when the Initialize method is invoked.
from m in Methods where m.IsUsedBy ("SpringApp.Initialize()") && !m.IsThirdParty select new { m }
This method initializes all the other classes with which it collaborates like LuaOpenGL, CMyMath and CGlobalRendering, and after it invokes the startup method. And here’s the dependency graph of the startup method.
External frameworks used
When developing a product, it’s preferable to not reinvent the wheel and reuse proven and mature frameworks. Let’s search for external types used by Spring.
from t in Types where t.IsUsedBy ("rts") select new { t }
Spring RTS uses mainly boost, this library is aimed at a wide range of C++ users and application domains. They range from general-purpose libraries like the smart pointer library, to operating system abstractions like Boost FileSystem, to libraries primarily aimed at other library developers and advanced C++ users, like the template metaprogramming (MPL) and domain-specific language (DSL) creation (Proto).
For example concerning network communication Spring uses the boost.asio library, which provides developers with a consistent asynchronous model using a modern C++ approach.
Conclusion
For any developer using the Spring RTS, or any new code base, it’s better to know sometimes how it works internally, for which the better approach is to go inside the code source. In this case we have found that the Spring RTS code is very simple to understand and evolve.