OGRE (Object-Oriented Graphics Rendering Engine) is a scene-oriented, flexible 3D rendering engine written in C++ designed to make it easier and intuitive for developers to produce applications utilizing hardware-accelerated 3D graphics. The class library abstracts the details of using the underlying system libraries like Direct3D and OpenGL and provides an interface based on world objects and other high-level classes.
Let’s analyze it with CppDepend to discover its design advantages.
The dependency graph shows the link between the OGRE3D projects:
The architecture is plugin oriented, it’s very useful to extend Ogre3D without any changes to the kernel project “OgreMain”. For that, the OgreMain project provides the needed classes to communicate with the plugins.
Let’s discover how the kernel project communicates with the plugins, for that let’s search which classes of OgreMain are used by the RenderSystem_GL plugin by executing this query:
SELECT TYPES WHERE IsDirectlyUsedBy “RenderSystem_GL”
The plugin uses some utility classes, model entities and override some abstract classes to integrate the plugin into the Ogre3D ecosystem.
Let’s filter the below result and search only the abstract classes used by the RenderSystem_GL plugin:
SELECT TYPES WHERE IsDirectlyUsedBy “RenderSystem_GL” AND IsAbstract
The plugin can override the scenes, textures, rendering and more. The Ogre3D provides an interesting API to customize easily the rendering, which makes our task to adapt it to fulfill our needs very easy.
Inheritance and Polymorphism
Using object-oriented approach can imply an overuse of the inheritance to make the most of the polymorphism concept.
Especially for OgreMain that represents the kernel of the Ogre3d framework, where many classes are designed to be overloaded by the plugin projects.
SELECT TYPES FROM PROJECTS “OgreMain” WHERE NbBaseClass >0
But multiple inheritances increase the complexity, and we have to use it carefully.
Let’s search for the classes with many base classes.
Only a few classes derived from more than one class.
For a plugin oriented architecture, the host must have many abstract classes to be more flexible and extensible.
Let’s search for abstract classes of OgreMain:
SELECT TYPES FROM PROJECTS “OgreMain” WHERE IsAbstract
CppDepend provides DSM graph, and we can triangularize this matrix to focus on red borders highly dependency cycle.
A dependency cycle exists between Ogre,Ogre::EmitterCommands and Ogre::OverlayElementCommands, having this dependency can be not problematic but avoiding this kind of dependency enforce loose coupling, this interesting post explain the benefit of layering.
Let’s search for the origin of dependency between Ogre and two other namespaces.
SELECT TYPES WHERE IsDirectlyUsedBy “Ogre”
The namespace Ogre use all Cmd classes from the other namespaces, and all those classes are used by Ogre::ParticleEmitter as static fields, ParticleEmitter add them to CmdParam dictionary.
Maybe it’s possible to do it differently to avoid this dependency cycle but it’s not a big issue.
The single responsibility principle states that a class should have more than one reason to change. Such a class is said to be cohesive. A high LCOM value generally pinpoints a poorly cohesive class. There are several LCOM metrics. The LCOM takes its values in the range [0-1]. The LCOMHS (HS stands for Henderson-Sellers) takes its values in the range [0-2]. Note that the LCOMHS metric is often considered as more efficient to detect non-cohesive types. LCOMHS value higher than 1 should be considered alarming.
SELECT TYPES WHERE LCOMHS > 0.95 AND NbFields > 10 AND NbMethods >10 AND !IsGlobal
only a few classes are considered as no cohesive.
Design patterns used
To ensure a class has only one instance, the better way is to use the singleton pattern.
Lets search which classes are a singleton:
SELECT TYPES WHERE DeriveFrom “Ogre.Singleton”
As we can observe almost all manager classes are a singleton, generally, a manager is a good candidate to be a singleton. And we can search for manager classes that not derived from Singleton
SELECT TYPES WHERE !DeriveFrom “Ogre.Singleton” AND NameLike “Manager$”
Only a few managers are not derived from Singleton, it’s normal because those classes can be instantiated many times.
The factory pattern is very useful to abstract the creation of objects, it enforces low coupling and high cohesion as explained in this post.
but having factory not implies that an instance is created by this factory because we can instantiate the class directly, and we have to define a rule to be sure that the factory is used for instantiation.
For example about Entity class we can define the following rule to discover each class instantiates it:
SELECT METHODS WHERE DepthOfCreateA “Ogre.Entity” == 1
As we can observe only EntityFactory instantiate the Entity class.
Manager classes give access to a subsystem, is very useful to modularise the project, and Ogre3d contains many managers, each one represents a different subsystem.
SELECT TYPES WHERE NameLike “Manager$”
Facade defines a higher-level interface that makes the subsystem easier to use, and we can detect facade for your project by using Efferent Coupling metric. The Efferent Coupling for a particular type is the number of types it directly depends on. Types where TypeCe > 50 are types that depend on too many other types. They are complex and have more than one responsibility. They are a good candidate for refactoring.
But sometimes in the case of a facade, having a high TypeCe can be normal.
Lets search for classes with high TypeCe:
SELECT TYPES ORDER BY TypeCe DESC
Not all of those classes are a facade and for each class of them we can maybe find an explanation why TypeCe is high, and maybe some classes can be refactored, and we confess that we don’t master Ogre3d to explain that.
And the facade mostly used is Root class, let’s see what subsystem this class use.
As we can observe Root class use almost all manager classes.
And we can search for a manager not accessible by Root by this following CQL query:
SELECT TYPES WHERE !IsDirectlyUsedBy “Ogre.Root” AND NameLike “Manager$” AND !IsAbstract
The observer is a pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. It is mainly used to implement event handling systems.
Ogre3d use Listener classes to implement the observer pattern.
Let’s search for Listener classes for OgreMain project.
SELECT TYPES FROM PROJECTS “OgreMain” WHERE NameLike “Listener$”
Ogre3d is very clean as a framework, very well designed and very well commented.You can easily understand the utility of the design patterns used, and its modularity can help you to accelerate the time of learning its capabilities.