Quantcast
Channel: GameDev.net
Viewing all articles
Browse latest Browse all 17825

Spring RTS Engineering Internals

$
0
0
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.

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

Attached Image: spring22.png

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

Attached Image: spring27.png

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

Attached Image: spring26.png

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

Attached Image: spring29.png

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

Attached Image: spring2.png

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.
The treemap view provides a useful way to represent the result of a CQLinq request, so we can visually see the types concerned by the request.

Attached Image: spring3.png

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

Attached Image: spring4.png

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.

Attached Image: spring16.png

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

Attached Image: spring6.png

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

Attached Image: spring7.png

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.

Attached Image: spring9.png

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 }

Attached Image: spring10.png

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.

Attached Image: spring11.png

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 }

Attached Image: spring25.png

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.

Viewing all articles
Browse latest Browse all 17825

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>